Skip to content

SSH access to DUTs

How ssh dut-X reaches each DUT through its isolated VLAN, and how to connect manually when a DUT is on mesh VLAN 200.


Static ProxyCommand

Each DUT SSH alias in ~/.ssh/config uses a static ProxyCommand that binds to the DUT's isolated VLAN interface via labgrid-bound-connect:

Host dut-belkin-1
    User root
    ProxyCommand sudo labgrid-bound-connect vlan100 192.168.1.1 22

labgrid-bound-connect uses socat with SO_BINDTODEVICE to force traffic through the correct VLAN interface. Since all DUTs share 192.168.1.1 on their isolated VLAN, binding to the interface is what distinguishes them.

The full SSH config template is in configs/templates/ssh_config_fcefyn.


VLAN lifecycle during tests

sequenceDiagram
    participant Test as pytest
    participant Switch as TP-Link switch
    participant DUT as DUT

    Note over DUT: Default: isolated VLAN (100-108)
    Test->>Switch: set_port_vlan(dut, 200)
    Note over DUT: Mesh VLAN 200
    Test->>DUT: run tests via SSHProxy(vlan200)
    Test->>Switch: restore_port_vlan(dut)
    Note over DUT: Back to isolated VLAN
Hold "Alt" / "Option" to enable pan & zoom
  • openwrt-tests: VLANs never change. DUTs always on isolated VLANs.
  • libremesh-tests: conftest_vlan.py moves ports to VLAN 200 at test start and always restores on teardown (even with LG_MESH_KEEP_POWERED=1, which only skips power-off).
  • Tests use their own SSH path (SSHProxy with hardcoded vlan200), not the ~/.ssh/config aliases.
  • SSH transport retry: SSHProxy automatically retries up to 3 times on exit code 255 (TCP/SSH transport errors). These are common during batman-adv/babeld convergence right after boot.
  • IP watchdog: After boot, a background script on each DUT re-applies the fixed mesh SSH IP every 3 seconds for 300 seconds, targeting br-lan when UP. This survives network restarts triggered by OpenWrt init scripts or batman-adv configuration.

VLAN switching: local vs remote

Only the lab host owns the switch credentials (SNMP access, switch-vlan CLI, dut-config.yaml). The developer machine does not.

conftest_vlan.py handles both scenarios transparently:

Execution LG_PROXY How switch-vlan runs
From lab host (CI, local dev) Not set subprocess.run(["switch-vlan", "dut", "200"])
From remote developer machine labgrid-fcefyn subprocess.run(["ssh", "labgrid-fcefyn", "switch-vlan dut 200"])
flowchart LR
    subgraph Remote["Developer machine"]
        pytest["pytest"]
    end
    subgraph Host["Lab host"]
        switch_vlan["switch-vlan"]
        switch["TP-Link switch<br/>(SNMP)"]
    end
    pytest -->|"ssh labgrid-fcefyn<br/>switch-vlan dut 200"| switch_vlan
    switch_vlan -->|SNMP| switch
Hold "Alt" / "Option" to enable pan & zoom

Result: Developers do not need to install switch-vlan, configure switch credentials, or have dut-config.yaml locally. VLAN operations are delegated to the host via SSH.


Manual access to mesh VLAN 200

If a test crashes before VLAN teardown, a DUT may be stuck on VLAN 200. From the lab host (where vlan200 and sudo NOPASSWD for labgrid-bound-connect exist):

sudo labgrid-bound-connect vlan200 <mesh_ssh_ip> 22

There are three addresses worth distinguishing during mesh tests:

  • 192.168.1.1: the default OpenWrt LAN IP. This is what isolated single-node access uses through vlan100-vlan108.
  • 10.13.200.x: the per-DUT mesh SSH/control IP. It is a stable secondary address added on br-lan so the host can reach each DUT uniquely while multiple nodes share VLAN 200.
  • 10.13.x.x excluding 10.13.200.x: the real LibreMesh address on br-lan. This is the address mesh assertions should use for ping, ARP, and routing checks.

Remote developer (LG_PROXY)

From a developer machine, the vlan200 interface lives on the lab host, not on the developer machine. The bound-connect must therefore run on the host via SSH. The test suite handles this automatically (see SSHProxy._build_ssh_cmd in conftest_mesh.py); for manual SSH from a machine:

ssh -o ProxyCommand="ssh ${LG_PROXY:-labgrid-fcefyn} sudo /usr/local/sbin/labgrid-bound-connect vlan200 <mesh_ssh_ip> 22" \
    root@<mesh_ssh_ip>
sequenceDiagram
    participant Machine as Dev machine
    participant Host as Lab host (LG_PROXY)
    participant DUT as DUT mesh (10.13.200.x)
    Machine->>Host: ssh (LG_PROXY)
    Host->>Host: sudo labgrid-bound-connect vlan200 IP 22
    Host->>DUT: TCP socket bound to vlan200
    Machine->>DUT: SSH session through nested ProxyCommand
Hold "Alt" / "Option" to enable pan & zoom

Pre-requisites on the host (already in place for labgrid-dev per host-config 3.4): SSH user must have sudo NOPASSWD for /usr/local/sbin/labgrid-bound-connect. No local dut-config.yaml nor labgrid-switch-abstraction install is required on the developer machine.

Per-DUT isolated VLAN and mesh SSH IP table: Rack cheatsheets - all DUTs.

To restore all ports to isolated VLANs after a crash:

switch-vlan --restore-all