build(docker): Add remote radio support in docker#201
build(docker): Add remote radio support in docker#201Lakshmi97-velampati wants to merge 18 commits into
Conversation
Add an optional otbr-radio Docker container that connects a Silicon Labs BRD2703 xG24 Explorer Kit to the Barton devcontainer via D-Bus, enabling Thread integration testing with real hardware. - Dockerfile.otbr-radio: builds image with cpcd and otbr-agent compiled for CPC transport; uses the URL spinel+cpc://cpcd_0?iid=1&iid-list=0 to handle both unicast and broadcast Spinel frames - compose.otbr-radio.yaml: compose overlay that shares a D-Bus socket volume with the barton service and maps RADIO_DEVICE at the same path on both the host and container sides - otbr-radio-entrypoint.sh: writes a fresh cpcd config on every start (uart_device_file, uart_hardflow: true), waits for "Daemon startup was successful" before launching otbr-agent, and auto-detects BACKBONE_IF from the host default route at runtime - setupDockerEnv.sh: auto-detects the backbone interface via ip route and writes it to docker/.env; Refs: BARTON-359 Signed-off-by: mvelam850 <munilakshmi_velampati@comcast.com>
There was a problem hiding this comment.
Pull request overview
Adds an optional “real USB radio” Thread Border Router path for Barton’s Docker-based dev environment by introducing an otbr-radio sidecar container (cpcd + otbr-agent) that shares a D-Bus socket volume with the main barton container, enabling Thread integration testing against real BRD2703 hardware.
Changes:
- Add an
otbr-radioDocker image + entrypoint that brings up D-Bus, Avahi, cpcd, then otbr-agent over CPC (spinel+cpc://). - Add a Compose overlay to run
otbr-radiowith host networking/privileges and share/var/run/dbuswithbarton. - Extend tooling/docs:
dockerw -Tflag,.envvariables (RADIO_DEVICE,BACKBONE_IF), and setup instructions.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| docs/THREAD_BORDER_ROUTER_SUPPORT.md | Adds end-to-end documentation for simulated vs real-radio Thread setup, including USB-IP guidance. |
| dockerw | Adds -T flag to include the OTBR radio compose overlay and start the otbr-radio service. |
| docker/setupDockerEnv.sh | Writes optional RADIO_DEVICE and BACKBONE_IF into docker/.env, with backbone IF auto-detection. |
| docker/README.md | Documents the new compose overlay and the -T flag. |
| docker/otbr-radio-entrypoint.sh | New entrypoint that orchestrates D-Bus, Avahi, cpcd, and otbr-agent startup for CPC-based RCP. |
| docker/Dockerfile.otbr-radio | New build for cpcd + otbr-agent (Silabs GSDK transport/CPC) and D-Bus policy. |
| docker/compose.otbr-radio.yaml | New compose overlay that runs otbr-radio privileged with host networking and shares D-Bus socket volume with barton. |
kfundecmcsa
left a comment
There was a problem hiding this comment.
I have a couple open questions, but requesting changes for the title. This effort should fall into the build commit type, not feat
Updated |
|
Be sure to take this PR out of draft when you are ready for re-review. |
2e68240
Thanks for testing this and for adding the BLE support. Apologies, I haven’t validated the commissioning flow yet. My checks were limited to confirming that the otbr-agent is running in the new container. I’ll verify the commissioning flow now with the BLE changes you’ve added and confirm the results. |
| # AF_BLUETOOTH (e.g. bluetoothctl) can run via nsenter. | ||
| - /proc/1/ns/net:/run/host-netns:ro | ||
| environment: | ||
| - DBUS_SYSTEM_BUS_ADDRESS=${DBUS_SYSTEM_BUS_ADDRESS:-unix:path=/var/run/otbr-dbus/system_bus_socket} |
| 2. If using Matter, configure and build your Matter SDK as described in [MATTER_SUPPORT.md](). | ||
| 3. If using Zigbee, see [ZIGBEE_SUPPORT.md](). | ||
| 4. If using OpenThread's Border Router, see [THREAD_BORDER_ROUTER_SUPPORT.md](). | ||
| 4. If using OpenThread's Border Router, see [REMOTE_RADIO_FOR_DEVELOPMENT.md](). |
| if echo "${HCI_DEVICES}" | grep -q "hci${BLE_ADAPTER_ID}"; then | ||
| pass "hci${BLE_ADAPTER_ID} is present in host network namespace" | ||
|
|
||
| HCI_STATUS=$(echo "${HCI_DEVICES}" | grep -A2 "hci${BLE_ADAPTER_ID}:" || true) |
| echo "[otbr-radio] Starting cpcd (instance: cpcd_0, device: ${RADIO_DEVICE})..." | ||
| CPCD_LOG="/tmp/cpcd.log" | ||
| : > "${CPCD_LOG}" | ||
| cpcd --conf "${CPCD_CONF}" > >(tee "${CPCD_LOG}") 2>&1 & |
hcitool uses the legacy HCI socket interface which permanently conflicts with bluetoothd's MGMT socket. Starting an LE scan via hcitool and killing it leaves the kernel MGMT layer stuck, causing all subsequent StartDiscovery calls to fail with InProgress. Changes: - Rewrite ble_verify_scan() to use hciconfig instead of hcitool lescan - Make ble_health_check() passive (read D-Bus properties only, no StartDiscovery/StopDiscovery) to avoid conflicts with Matter SDK scans - Remove hciconfig down and hcitool cmd HCI_Reset from init sequence; simplify to hciconfig up with down/up recovery cycle - Update usbip-validate.sh checks for revised BLE stack behavior
| # AF_BLUETOOTH (e.g. bluetoothctl) can run via nsenter. | ||
| - /proc/1/ns/net:/run/host-netns:ro | ||
| environment: | ||
| - DBUS_SYSTEM_BUS_ADDRESS=${DBUS_SYSTEM_BUS_ADDRESS:-unix:path=/var/run/otbr-dbus/system_bus_socket} |
| echo "[otbr-radio] Starting private D-Bus system bus at ${DBUS_SYSTEM_BUS_ADDRESS}..." | ||
| mkdir -p "${DBUS_DIR}" | ||
| if [ -S "${DBUS_SOCKET_PATH}" ]; then | ||
| echo "[otbr-radio] Removing stale D-Bus socket ${DBUS_SOCKET_PATH}..." | ||
| rm -f "${DBUS_SOCKET_PATH}" | ||
| fi | ||
| dbus-daemon --config-file=/etc/otbr-dbus.conf --fork --nopidfile | ||
| echo "[otbr-radio] Private D-Bus started." | ||
|
|
||
| ############################################################################### | ||
| # 2. Validate the USB radio device | ||
| ############################################################################### | ||
| if [ -z "${RADIO_DEVICE}" ]; then | ||
| echo "[otbr-radio] ERROR: RADIO_DEVICE is not set." >&2 | ||
| echo "[otbr-radio] Set it in docker/.env or export before starting:" >&2 | ||
| echo "[otbr-radio] export RADIO_DEVICE=/dev/ttyACM0" >&2 | ||
| echo "[otbr-radio] See docs/REMOTE_RADIO_FOR_DEVELOPMENT.md for setup instructions." >&2 | ||
| exit 1 | ||
| fi |
| <listen>unix:path=/var/run/otbr-dbus/system_bus_socket</listen> | ||
| <auth>ANONYMOUS</auth> | ||
| <allow_anonymous/> | ||
| <policy context="default"> | ||
| <allow own="io.openthread.BorderRouter.wpan0"/> | ||
| <allow send_destination="io.openthread.BorderRouter.wpan0"/> | ||
| <allow send_interface="*"/> | ||
| <allow user="*"/> | ||
| <allow own="*"/> | ||
| <allow send_type="*"/> | ||
| <allow receive_sender="*"/> | ||
| <allow send_destination="*"/> | ||
| <allow receive_type="*"/> | ||
| <allow receive_sender="*"/> |
| # Use timeout because bluetoothctl blocks if bluetoothd is not running. | ||
| BT_LIST=$(timeout 5 sudo nsenter --net="${HOST_NETNS}" bluetoothctl list 2>/dev/null || true) |
| # scripts/usbip-attach-local.sh user@remote-server | ||
| # scripts/usbip-attach-local.sh remote-server # uses current username | ||
| # | ||
| # Leave the terminal open while you work. Press Ctrl-C to close the tunnel. | ||
| # Run scripts/usbip-detach-local.sh to clean up. |
| char *endPtr = nullptr; | ||
| unsigned long val = strtoul(env, &endPtr, 10); | ||
|
|
||
| if (endPtr != env && *endPtr == '\0') | ||
| { | ||
| adapterId = static_cast<uint32_t>(val); | ||
| icInfo("Using BLE adapter hci%u from %s", adapterId, BLE_CONTROLLER_ADAPTER_ID_ENV); |
Add infrastructure for running the otbr-radio container against a remote cpcd instance over TCP, and work around two Silicon Labs CPC BLE firmware bugs that prevent BLE scanning and connections from working through the CPC radio. Remote CPC mode =============== When CPC_REMOTE_HOST is set, the entrypoint starts CPC socket proxy clients instead of running cpcd locally. The proxy tunnels CPC SEQPACKET Unix sockets over TCP connections to a remote host running cpc_remote_access.sh in server mode. Six proxy instances are started: ctrl, reset, and data+event for both the Bluetooth RCP (endpoint 14) and Thread/Spinel (endpoint 12) endpoints. New files: - scripts/remote-radio/cpc_socket_proxy.py: Bidirectional SEQPACKET-over-TCP proxy with self-healing reconnect, TCP keepalive for fast stale-session detection, and automatic session eviction in server mode (CPC endpoints allow only one client). - scripts/remote-radio/cpc_remote_access.sh: Server-side watcher that starts and monitors proxy server instances on the remote host where cpcd runs. CPC BLE firmware workarounds (hci_pty_proxy.py) =============================================== The Silicon Labs BRD2703 xG24 CPC BLE firmware has two HCI compatibility bugs that prevent BLE from working with the Linux kernel (tested on 6.8.0 / BlueZ 5.72): Bug 1: Extended Scan Disable wrong opcode The firmware advertises LE Extended Advertising support in its feature set, but when the kernel sends LE Set Extended Scan Enable (opcode 0x2042) to disable scanning, the firmware responds with the wrong opcode (legacy 0x200c instead of 0x2042). The kernel discards this unexpected response and the 0x2042 command times out. Because scanning cannot be stopped, all subsequent LE Create Connection commands are rejected with "Command Disallowed" (HCI status 0x0c). Fix: The proxy intercepts the LE Read Local Supported Features response during controller init and clears the Extended Advertising feature bit (byte 1, bit 4 = 0x10). This forces the kernel to use legacy BLE scan commands (0x200b/0x200c) which the firmware handles correctly. Bug 2: Enhanced Connection Complete v2 event After clearing Extended Advertising support, the kernel only registers a handler for the legacy LE Connection Complete event (subevent 0x01). However, the firmware sends LE Enhanced Connection Complete v2 (subevent 0x29, a BT 5.2+ event) when a connection is established. The kernel silently discards this unknown subevent, the connection times out (HCI status 0x08), and the firmware is left with a phantom connection that generates periodic "ACL packet for unknown connection handle" errors in dmesg. Fix: The proxy translates Enhanced Connection Complete events (subevent 0x29 for v2, 0x0A for v1) into the legacy format (subevent 0x01) by stripping the Resolvable Private Address fields (12 bytes), Advertising_Handle (1 byte), and Sync_Handle (2 bytes), and rewriting the parameter length accordingly. New file: - scripts/remote-radio/hci_pty_proxy.py: HCI UART (H4) PTY proxy that sits between bt_host_cpc_hci_bridge and btattach. Supports optional debug logging via HCI_PROXY_DEBUG env var for troubleshooting HCI event flow. Entrypoint changes (otbr-radio-entrypoint.sh) ============================================= - Add remote CPC mode: when CPC_REMOTE_HOST is set, start CPC socket proxy clients and skip local cpcd startup. - Integrate HCI PTY proxy between bt_host_cpc_hci_bridge and btattach (section 5b). Falls back gracefully if the proxy script is not mounted. - Remove stale pts_hci symlink before starting bt_host_cpc_hci_bridge to prevent the PTY wait loop from seeing a stale file from a previous run. - Simplify BLE init sequence: replace ble_verify_scan() and ble_health_check() with a deterministic down/up/HCI-Reset cycle followed by bluetoothd start. The previous approach used hcitool lescan for verification which poisoned the MGMT layer. - Simplify ble_monitor(): remove D-Bus health checks and bridge restart logic; just watch btattach and restart the BLE chain when it exits. Compose changes (compose.otbr-radio.yaml) ========================================== - Bind-mount cpc_socket_proxy.py, hci_pty_proxy.py, and the entrypoint script into the container. - Add CPC_REMOTE_HOST, CPC_REMOTE_PORT, CPC_INSTANCE env vars. Other changes ============= - CommissioningOrchestrator.cpp: Close BLE connection after ThreadNetworkEnable completes to free the CPC radio for Thread traffic. BLE and Thread share the single CPC radio and cannot operate simultaneously. - devcontainer.json: Set DEBUGINFOD_URLS="" to prevent GDB from hanging on symbol downloads from the Ubuntu debuginfod server.
|
Missing or incorrect copyright headers in the following files: |
|
Missing or incorrect copyright headers in the following files: |
…ote radio stack Remove CloseBleConnection() after ThreadNetworkEnable ------------------------------------------------------ CPC multiplexes BLE and Thread independently via separate endpoints (ep14 for Bluetooth RCP, ep12 for 802.15.4/Spinel), so closing the BLE connection after Thread network enable is unnecessary. Worse, it prevents the Matter SDK from extending the commissioning failsafe over BLE during CASE-over-Thread retries, causing commissioning to fail when the first CASE attempt doesn't succeed immediately. HCI PTY proxy: intercept Read Local Name (0x0C14) -------------------------------------------------- The CPC firmware does NOT support HCI Read Local Name. It returns a 1-byte "Unknown HCI Command" error instead of the expected 249-byte response. The kernel logs "unexpected cc 0x0c14 length: 1 < 249" and mishandles the command completion — this corrupts the HCI command queue, causes background scan-stop timeouts (-ETIMEDOUT), and leaves bluetoothd in a permanently stuck state where StartDiscovery() returns "InProgress" even though no scan is active. The proxy now intercepts outgoing Read Local Name commands and returns a synthetic success response with a placeholder name so the unsupported command never reaches the CPC firmware. Also adds Extended Advertising command interception (bytes 36-37 opcode stripping) and LE feature bit stripping. CPC socket proxy: support concurrent clients with retry -------------------------------------------------------- Previously the proxy evicted stale sessions on new connections. Now multiple TCP clients can connect concurrently (e.g. both the HCI bridge and otbr-agent need their own ctrl session). Each TCP client gets its own independent Unix socket connection to cpcd. Added retry logic with backoff for endpoint sockets that cpcd creates on-demand after the remote client opens them via the control socket. otbr-radio entrypoint: resilient BLE bridge lifecycle ------------------------------------------------------ - Increase bt_host_cpc_hci_bridge wait timeout from 15s to 30s for remote CPC connections - Convert bridge startup failures from fatal errors to warnings; the BLE monitor loop will retry - Add BLE monitor loop that detects bridge/proxy crashes and restarts the full BLE stack (bridge -> proxy -> btattach -> bluetoothd) - Add process reaping for zombie children - Use python3 -u for unbuffered proxy output CPC remote access: pre-start well-known endpoint proxies --------------------------------------------------------- Pre-start TCP listeners for well-known CPC endpoints (ep12 for 802.15.4, ep14 for Bluetooth) before any remote client connects. The proxy retries the local Unix socket connection with backoff, giving cpcd time to create endpoint sockets on-demand. Added health check loop to restart well-known proxies if they die. setupDockerEnv.sh: persist CPC remote radio settings ----------------------------------------------------- Preserve CPC_REMOTE_HOST, CPC_REMOTE_PORT, and CPC_INSTANCE in docker/.env across setupDockerEnv.sh invocations, matching the existing behavior for RADIO_DEVICE and BACKBONE_IF. compose.otbr-radio.yaml: mount cpc_validate.sh ----------------------------------------------- Bind-mount the CPC validation/diagnostic script into the otbr-radio container at /opt/cpc-proxy/cpc_validate.sh. Add cpc_validate.sh diagnostic script -------------------------------------- Comprehensive validation script for the full CPC + BLE + Thread stack in the otbr-radio container. Checks CPC connectivity, BLE bridge, HCI proxy, BlueZ stack, BLE scanning, Thread interface, and multi-user container detection safety.
|
Missing or incorrect copyright headers in the following files: |
Replace the USB-IP and CPC socket proxy remote radio approach with a
simpler remote serial tunnel architecture. The new design uses a single
Python script on the workstation that relays serial bytes over an SSH
reverse tunnel, eliminating the need for USB-IP kernel modules, multiple
helper scripts, and complex CPC socket proxying.
New files:
- scripts/remote-radio/remote-serial.py: Workstation-side relay that
opens the local Silicon Labs radio serial port, starts a TCP server,
and establishes an SSH reverse tunnel to the dev server. Features
auto-detection of radio ports, per-user port isolation (base 20000 +
UID), persistent serial port across TCP reconnects, stale tunnel
cleanup, and auto-reconnect for both serial and SSH.
- scripts/remote-radio/validate.sh: Comprehensive 8-section validation
script covering container environment, D-Bus, radio connection, CPC,
BLE chain, HCI, otbr-agent, and known pitfalls. Auto-detects and
re-execs into the otbr-radio container via Docker CLI or Docker Engine
API (for environments without the docker command).
- docker/setup-thread-routes.sh: Configures IPv6 routing in the barton
container so it can reach Thread mesh devices via the otbr-radio
container. Adds a fd00::/8 ULA catch-all route through otbr-radio's
bridge address. Runs as postStartCommand in the devcontainer.
Modified files:
- docker/otbr-radio-entrypoint.sh:
- Add remote serial tunnel mode: when RADIO_HOST/RADIO_PORT are set,
use socat to bridge TCP to a PTY instead of a local USB device
- Resolve host.docker.internal to IPv4 (SSH tunnel binds IPv4 only)
- Remove wait-slave from socat (prevents TCP lifecycle coupling to
PTY slave state during cpcd init retries)
- Use no-data TCP probe (: >/dev/tcp/) instead of echo to avoid
sending garbage bytes to the radio during connectivity checks
- Always set uart_hardflow: true (radio firmware requires it; cpcd
exits with FATAL on mismatch even though CRTSCTS is a no-op on PTY)
- Add stdbuf -oL for cpcd output buffering (ensures grep detects
the ready message promptly)
- Set accept_ra=2 on backbone interface (kernel drops RAs when
forwarding=1 without this)
- docker/compose.otbr-radio.yaml:
- Add sysctls for IPv6 forwarding and NDP proxy on otbr-radio
- Add RADIO_PORT/RADIO_HOST environment variables
- Add extra_hosts for host.docker.internal resolution
- docker/setupDockerEnv.sh:
- Replace CPC_REMOTE_HOST/PORT/INSTANCE with RADIO_PORT/RADIO_HOST
- .devcontainer/devcontainer.json:
- Add postStartCommand for Thread IPv6 route setup
- docs/REMOTE_RADIO_FOR_DEVELOPMENT.md:
- Complete rewrite covering both local USB and remote serial tunnel
Deleted files (replaced by remote-serial.py + validate.sh):
- scripts/remote-radio/cpc_remote_access.sh
- scripts/remote-radio/cpc_socket_proxy.py
- scripts/remote-radio/cpc_validate.sh
- scripts/remote-radio/usbip-attach-local.sh
- scripts/remote-radio/usbip-attach-remote.sh
- scripts/remote-radio/usbip-detach-local.sh
- scripts/remote-radio/usbip-detach-remote.sh
- scripts/remote-radio/usbip-validate.sh
| self._server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | ||
| self._server_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) | ||
| self._server_sock.settimeout(2.0) | ||
| self._server_sock.bind(("127.0.0.1", self.listen_port)) |
Add an optional otbr-radio Docker container that connects a Silicon Labs BRD2703 xG24 Explorer Kit to the Barton devcontainer via D-Bus, enabling Thread integration testing with real hardware.
Refs: BARTON-359