Appearance
The mirrors
The mirror is the point of the whole exercise: a local interface on the Brain that behaves like the remote one, so standard Linux tooling works on a segment it is not wired to.
Brain (Linux) STM32 Gateway (Zephyr)
┌─────────────────────────────────┐ ┌───────────────────────────┐
│ user tools (candump, picocom) │ │ portal firmware │
│ │ │ │ (unconfirmed MCUboot │
│ portal0 (virtual CAN) or │ │ image, reverts on │
│ /dev/tty-portal0 (PTY) │ │ power-cycle) │
│ │ │ │ │
│ muxen-portal (daemon) │ │ MUXEN side remote side │
│ │ │ ISO-TP │ CAN bus or │
│ can0 ─────────────────┼──────────┼── (tunnel) ── serial ─────┼── remote segment
│ │ │ tunnel │ │ (devices under
│ muxen-uds ── firmware ────┼──────────┼── MCUboot / app fw │ diagnosis)
└─────────────────────────────────┘ upload └───────────────────────────┘| Path | Carrier |
|---|---|
| Firmware upload, device reset, scan | muxen-uds (existing, unchanged) |
| Tunnel data + control plane | ISO-TP on dedicated control and data ID pairs (ACCESS = 3) on the MUXEN bus |
| Access to a remote CAN bus | SocketCAN portal0 |
| Access to a remote serial port | PTY /dev/tty-portal0[-chN] |
| Access to a remote LIN bus | SocketCAN portal0chN, one per channel |
The mirror type follows the gateway type announced in HELLO; it is never something you pass on the command line. Both mirror kinds are virtual, so kernel-side line settings on them are meaningless — the remote settings are session parameters the gateway applies to its physical interface.
CAN mirror
A vxcan pair whose user-facing end is named portal0 (configurable). The daemon binds the peer end. If vxcan is unavailable in the kernel it falls back to vcan, with the daemon echo-filtering its own injected frames.
sh
candump portal0
candump portal0,0:0,#FFFFFFFF # data frames AND error frames
cansend portal0 18EEFF00#0011223344556677Behaviours worth knowing:
- The mirror declares itself classic. The user end is created with MTU 16, so an application attempting a CAN-FD
write()fails synchronously withEMSGSIZE, exactly as on a real classic controller — no silent drop. The FD transport of the tunnel never implies an FD remote bus. - RTR frames are transmitted on the remote bus, so a tool that polls with remote frames works.
- Error frames written by the user are dropped and counted. Emitting an "error frame" on a physical bus is meaningless.
- Remote CAN errors appear as SocketCAN error frames on
portal0, injected by the daemon — possible precisely because the mirror is virtual. Arm the error mask or you will not see them. - Fresh TX-queue drops on the gateway raise a
CAN_ERR_CRTL(TX overflow) error frame onportal0, so an application watching error frames sees the loss in near real time instead of pollingmuxen-portal status.
Fidelity limits
- No local echo. On a real interface, frames you send are echoed to other local sockets once ACKed on the bus. On the mirror they are not, and the daemon does not simulate it — a fake echo would claim an ACK that may never happen downstream.
- Your own TX is never mirrored back as RX, exactly as a CAN controller does not receive its own frames.
candump portal0shows remote-originated traffic only: your stimuli are absent, only the replies show.
Serial mirrors
One PTY per open channel. The daemon owns the master sides; each slave is exposed through a stable symlink <name>-chN (/dev/tty-portal0-ch1, …), and <name> itself (/dev/tty-portal0) points at the first channel opened — so single-channel sessions keep the short name.
sh
picocom -b 19200 /dev/tty-portal0-ch2
stty -F /dev/tty-portal0-ch2 9600
muxen-portal open-channel 3 --rate 9600 # additive, no re-uploadLine settings follow termios
Local termios settings on the PTY are ignored except baudrate and framing changes, which the daemon detects and forwards to the gateway as a hot re-OPEN. So stty and picocom -b transparently reconfigure the remote port, in milliseconds and with no re-upload.
The daemon keeps the applied configuration as a dedup cache and forwards only real changes. Tools that re-assert identical settings at startup — picocom, pyserial and most others do — cause no re-OPEN and no remote-port restart.
Rejected changes fail cleanly. A request invalid on its face (a baud above the gateway's advertised maximum, 5 or 6 data bits, mark/space parity, 1.5 stop bits) fails fast locally without a tunnel round trip; a plausible one the gateway refuses fails on the reply. Either way the daemon restores the PTY termios to the effective configuration, so an application following the POSIX contract — re-read after tcsetattr, as real UART drivers require — sees immediately that the request did not take. The rejection is logged with the translated reason and counted as termiosRejected in status.
The one accepted limit: between the application's tcsetattr and the restore (microseconds locally, tens of milliseconds via a gateway refusal) a tcgetattr shows the requested value. The termios API is local and synchronous; there is no asynchronous error channel.
Out of scope: break and idle conditions, 9-bit modes, and exotic ioctls (modem lines, break) are not forwarded.
Backpressure
Remote RX passes through a per-channel daemon-side ring (default 64 KiB, pty-buffer) and is written to the PTY master non-blocking.
| Situation | Behaviour |
|---|---|
| No reader on the slave | Incoming bytes are discarded and counted without accumulating. The buffer is purged when a reader attaches, so a tool connecting mid-session sees fresh stream, not a 30 s backlog. |
| Reader present but slow | Ring overflow: oldest dropped, counted. |
| No reader for ~3 s | The daemon sends FLOW(0) and the gateway stops shipping a stream the Brain would discard — sparing the whole MUXEN bus. Any reader attaching, or any write through the PTY, resumes it. |
Bridging, keep-alive and control never block on the PTY. A blocking write on a full master would freeze the daemon, starve the PING and revert the gateway; that is the failure this rule exists to forbid.
status exposes reader presence, the paused state and both drop counters.
LIN mirrors
One vxcan per LIN channel, named <name>chN — portal0ch1, portal0ch2, portal0ch3. This is the per-channel shape of the serial PTYs with the plumbing of the CAN mirror, reused verbatim.
The base name is capped at 11 characters so that <name>chN and its vxcan peer <name>chNd both fit a 15-character interface name.
The sllin mapping
Frames follow the mapping the Linux sllin line discipline already uses to expose a LIN bus as SocketCAN, so can-utils reads and drives a LIN bus with no LIN-specific tooling on the Brain:
| Frame field | LIN meaning |
|---|---|
| CAN ID | LIN frame ID, 0x00–0x3F, standard (11-bit) frame. PID parity is computed and checked by the gateway, so the Brain always sees the bare ID |
| payload | the response bytes, checksum stripped and verified |
RTR | header only. Received: a header went by with no response. Sent: emit this header and publish whatever answers |
CAN_ERR_FLAG | a LIN error record, with the class in data[0] |
sh
$ candump -td portal0ch1,0:0,#FFFFFFFF
portal0ch1 022 [4] A1 B2 C3 D4 # slave answered header 0x22
portal0ch1 03C [0] remote request # header seen, no response
portal0ch1 20000004 [8] ... # ERR frame: checksum error
$ cansend portal0ch1 022#R # master: header, capture response
$ cansend portal0ch1 030#AABBCCDD # master: header + publish dataError classes in data[0]:
| Value | Class | Meaning |
|---|---|---|
| 0x01 | CHECKSUM | response checksum mismatch |
| 0x02 | FRAMING | UART framing or bit error |
| 0x03 | NO_RESPONSE | header sent or seen, nothing answered before the timeout |
| 0x04 | PID_PARITY | bad PID parity on an observed header |
| 0x05 | OVERRUN | UART overrun |
Diagnostic IDs 0x3C/0x3D get no special treatment — they appear as ordinary frames, which is what a diagnostician wants to see.
Master or monitor
Per channel, selected at open; --listen-only picks monitor.
- Master — the Brain drives the schedule, one header per frame you send. This is the operationally necessary role: on these installations the MUXEN board is the LIN master, so with the product firmware swapped out nothing else generates headers and a passive-only portal would mirror a silent bus. Jitter is tunnel latency (milliseconds), which is fine for diagnosis but does not reproduce a timing-critical production schedule.
- Monitor — the gateway never transmits and only reports what a foreign master puts on the wire. A write to the mirror is dropped and counted rather than rejected, the same contract as a full TX queue.
The checksum rule is --lin-checksum classic|enhanced|auto, default auto (classify per frame by the LIN 2.x rule; IDs 0x3C–0x3F are always classic). The applied value comes back in STATUS and shows in stats.
Reconfiguration is set-rate and open-channel, as on serial. There is no termios equivalent — a vxcan carries no line settings, so the remote baud only ever changes through an explicit command.
Slave emulation and gateway-resident schedule tables are not supported.
Mirror lifetime and permissions
One session gives one mirror per open channel: exactly one on a CAN gateway, one PTY per channel opened on serial, one vxcan per channel opened on LIN.
Mirrors exist only while a session is active. They are destroyed immediately on a deliberate close; on session loss they linger for mirror-linger (default 180 s) while the daemon re-establishes the session and resumes on the same mirrors, so external diagnostic tools holding portal0 or a PTY open survive the outage.
Access is deliberately wide open: PTY slaves are created 0666 and portal0 is an ordinary SocketCAN interface, usable without privileges like any raw CAN socket. Shell access to the Brain already implies full unauthenticated access to can0, so the mirrors grant nothing new.
Timing fidelity
This is the one place where a mirror is not like the real interface, and it matters when you interpret what you see.
- RX traffic is injected as it arrives, so tools see local injection time. The gateway's own microsecond timestamp is kept in the daemon's statistics only.
- Original spacing is not re-simulated. The tunnel preserves byte order and contiguity, not silences. Every inter-byte gap you could measure on the Brain is an artefact of tunnel batching.
- Inter-frame gaps remain the application's responsibility — Modbus RTU's 3.5-character boundary, for instance — exactly as on a local port with a deep buffer.
- TX is forwarded immediately, and remote transmission is confirmed only implicitly through the STATUS TX counter. There is no per-frame acknowledgment.
Because timing cannot survive the tunnel, the shape of the traffic is measured on the gateway instead and shipped as a histogram — see muxen-portal timing in Monitoring a session.
