feat(usbgadget): add CDC-NCM (Ethernet over USB) function#1470
Open
mcuelenaere wants to merge 3 commits into
Open
feat(usbgadget): add CDC-NCM (Ethernet over USB) function#1470mcuelenaere wants to merge 3 commits into
mcuelenaere wants to merge 3 commits into
Conversation
mcuelenaere
added a commit
to mcuelenaere/kvm
that referenced
this pull request
May 26, 2026
Adds an `inet jetkvm` table with a single `input_usb0` chain that drops TCP and UDP arriving on usb0. ICMP/ICMPv6 (including NDP and ping) are untouched so the link stays debuggable. Installed by applyNcmFirewall when NCM is toggled on, removed by removeNcmFirewall on toggle off. Fail-closed: if modprobe or any nftables operation fails, the link is rolled back and bringUpNcmInterface returns the error -- no scenario where usb0 is up without the firewall in place. Idempotent: a stale table from a previous run is deleted before the fresh one is built. The rv1106 rootfs ships nf_tables.ko in /lib/modules but does not auto-load it (kernel module auto-load via NETLINK_NETFILTER never fires here), so applyNcmFirewall does a lazy `modprobe nf_tables` on first use. Match expressions (meta iifname, meta l4proto, cmp, drop) are all baked into nf_tables.ko on 5.10, so no additional modprobes are needed for the production rules. This closes the security gap that was previously documented as a known issue in PR jetkvm#1470: with this in place, the web UI, mDNS, SSH and any other system services are unreachable from the target host over the CDC-NCM link, while ICMPv6 (NDP + ping) keeps the link itself debuggable. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
mcuelenaere
added a commit
to mcuelenaere/kvm
that referenced
this pull request
May 26, 2026
Adds an `inet jetkvm` table with a single `input_usb0` chain that drops TCP and UDP arriving on usb0. ICMP/ICMPv6 (including NDP and ping) are untouched so the link stays debuggable. Installed by applyNcmFirewall when NCM is toggled on, removed by removeNcmFirewall on toggle off. Fail-closed: if modprobe or any nftables operation fails, the link is rolled back and bringUpNcmInterface returns the error -- no scenario where usb0 is up without the firewall in place. Idempotent: a stale table from a previous run is deleted before the fresh one is built. The rv1106 rootfs ships nf_tables.ko in /lib/modules but does not auto-load it (kernel module auto-load via NETLINK_NETFILTER never fires here), so applyNcmFirewall does a lazy `modprobe nf_tables` on first use. Match expressions (meta iifname, meta l4proto, cmp, drop) are all baked into nf_tables.ko on 5.10, so no additional modprobes are needed for the production rules. This closes the security gap that was previously documented as a known issue in PR jetkvm#1470: with this in place, the web UI, mDNS, SSH and any other system services are unreachable from the target host over the CDC-NCM link, while ICMPv6 (NDP + ping) keeps the link itself debuggable. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4a6db8c to
532f30c
Compare
Adds a new USB gadget function exposing CDC-NCM so the target host sees a
USB Ethernet device. Off by default; toggled via setUsbDeviceState("ncm",
true) or the new "Enable Ethernet over USB (CDC-NCM)" checkbox under the
custom USB devices preset.
host_addr and dev_addr are derived deterministically from GetDeviceID()
via SHA-256, with the locally-administered bit set, so MACs stay stable
across reboots and never collide across devices on the same host.
Reachability uses IPv6 link-local only: as soon as the link comes up, the
kernel auto-assigns fe80::/10 from the dev_addr MAC via modified EUI-64.
This works zero-config on Windows, macOS, and Linux (the host's NM/NetCfg
brings the new netdev up; on minimal Linux setups `ip link set <iface> up`
is needed once). IPv4 link-local (APIPA) is intentionally not configured
here -- it requires an extra address on our side and host-side fallback
support that varies by distro; defer to a follow-up if needed.
Bring-up/teardown of the usb0 netdev runs as a post-transaction hook in
configureUsbGadget and uses vishvananda/netlink (already a dependency
via pkg/nmlite/link) rather than shelling out to `ip`.
Scope is deliberately minimal: no NAT, no DHCP server, no internet
sharing, no bridge mode, no other Ethernet protocols. This is the base
layer that future work (clipboard sync over IP, file transfer, etc.) can
build on.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds an `inet jetkvm` table with a single `input_usb0` chain that drops TCP and UDP arriving on usb0. ICMP/ICMPv6 (including NDP and ping) are untouched so the link stays debuggable. Installed by applyNcmFirewall when NCM is toggled on, removed by removeNcmFirewall on toggle off. Fail-closed: if modprobe or any nftables operation fails, the link is rolled back and bringUpNcmInterface returns the error -- no scenario where usb0 is up without the firewall in place. Idempotent: a stale table from a previous run is deleted before the fresh one is built. The rv1106 rootfs ships nf_tables.ko in /lib/modules but does not auto-load it (kernel module auto-load via NETLINK_NETFILTER never fires here), so applyNcmFirewall does a lazy `modprobe nf_tables` on first use. Match expressions (meta iifname, meta l4proto, cmp, drop) are all baked into nf_tables.ko on 5.10, so no additional modprobes are needed for the production rules. This closes the security gap that was previously documented as a known issue in PR jetkvm#1470: with this in place, the web UI, mDNS, SSH and any other system services are unreachable from the target host over the CDC-NCM link, while ICMPv6 (NDP + ping) keeps the link itself debuggable. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The RV1106 dwc3 controller has a limited number of IN (device->host) and OUT
(host->device) endpoints -- separate pools, ~7 each. Each gadget function
claims some: HID mice/wake and audio capture take 1 IN; the keyboard takes
1 IN + 1 OUT (LED reports); mass storage takes 1 IN + 1 OUT; CDC-ACM and
CDC-NCM take 2 IN + 1 OUT each. When the enabled set exceeds either pool a
function silently fails to allocate its endpoint(s) -- CDC-NCM in particular
comes up looking connected (RX works) while TX is a black hole. That's
painful to diagnose, so warn before the user commits to an over-budget combo.
- internal/usbgadget/endpoints.go: per-function {in,out} endpoint cost map,
the two budget constants (with the empirical derivation documented; the IN
ceiling of 7 is confirmed on-device, OUT is assumed symmetric), and
ExceedsEndpointBudget. The true low-level limiter may be dwc3 TX-FIFO SRAM
rather than a raw count, but the effective ceiling is the same; FIFO RAM is
not modeled separately.
- internal/usbgadget/config.go: extract isGadgetConfigItemEnabledForDevices so
endpoint demand can be computed for any hypothetical Devices selection.
- jsonrpc.go: getUsbEndpointReport RPC returning {exceedsBudget} for a given
device set.
- UsbDeviceSetting.tsx: query it live as toggles change and show an inline
amber warning when the set exceeds the budget, suggesting the user free an
endpoint (e.g. disable Relative Mouse).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
532f30c to
a62baa7
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refs #642
Summary
Adds CDC-NCM (Ethernet over USB) as a new USB gadget function — a base layer for future work like eg clipboard sync over IP. Off by default, toggled via the new "Enable Ethernet over USB (CDC-NCM)" checkbox under custom USB devices. Reachable from the host over IPv6 link-local (
fe80::) with zero host-side config on Windows, macOS, and Linux.Why CDC-NCM (not ECM/RNDIS)
Modern cross-platform standard with inbox drivers on Windows 10+, macOS, and Linux. ECM is older and not native on Windows; RNDIS is Microsoft-proprietary and deprecated. A prior attempt (#459) added all four protocols plus NAT and internet sharing but was closed when the
usbgadgetpackage was refactored; the kernel-side prerequisite is now merged (rv1106-system#16).How it works
Devices.Ncmtoggle andncmConfiggadget item registeringncm.usb0.host_addranddev_addrMACs are derived deterministically fromGetDeviceID()(SHA-256, with locally-administered + unicast bits set correctly), so the IPv6 link-local address (kernel-derived fromdev_addrvia modified EUI-64) is also stable across reboots.configureUsbGadgetcommits, a post-transaction hook inusbgadgetusesvishvananda/netlink(already a dep) to bringusb0up and ensures the per-interface IPv6 sysctl isn't disabled. Tear-down on toggle off.github.com/google/nftables(pure Go, netlink): aninet jetkvmtable with one chaininput_usb0that drops inbound TCP and UDP arriving onusb0. ICMP/ICMPv6 (including NDP and ping) is intentionally untouched so the link stays debuggable. Fail-closed: if the firewall can't be installed,usb0is rolled back down.nf_tables.kois loaded lazily viamodprobeon first toggle (the rv1106 rootfs ships the module but doesn't auto-load it).UsbDeviceSetting.tsxin the "Custom" preset.getUsbEndpointReportRPC) and warns before the user commits to an over-budget combination.Verification
SKIP_UI_BUILD=1 make build_dev).tsc --noEmit) and lint (oxlint) clean.ncm.usb0present in configfs,usb0UP withfe80::.../64,ping6 fe80::<mac>%en<N>works immediately.nft list table inet jetkvmshows theinput_usb0chain with the two drop rules; counter on a diagnostic rule confirms packets arrive onusb0.curl -g 'http://[fe80::<mac>%en<N>]/'from the host times out (TCP dropped silently). Same for:443,ssh -6, mDNS over usb0.eth0web UI, mDNS, and SSH continue to work normally (rules are scoped viaiifname == "usb0").nft list table inet jetkvmreturns "No such table";usb0netdev disappears with the gadget rebind.Host isolation on usb0
When the toggle is on, an nftables firewall is installed that drops all inbound TCP and UDP on
usb0, so the web UI (:80/:443), mDNS, Dropbear/SSH (:22), and any other system servicejetkvm_appdoesn't directly own are unreachable from the target host. ICMPv4 / ICMPv6 (including NDP and ping) stay allowed so the link remains debuggable and IPv6 neighbor discovery keeps working. This addresses the security concern that a compromised target host would otherwise be able to reach the management plane that's controlling it.Scope and caveats:
modprobe nf_tablesor the netlink Flush fails,bringUpNcmInterfacereturns the error and rollsusb0back down — no scenario whereusb0is exposed without the firewall.inet jetkvmtable; nothing else on the device uses nftables today, but the dedicated namespace future-proofs against collisions.USB endpoint budget
The RV1106 dwc3 controller exposes IN (device→host) and OUT (host→device) endpoints as separate pools — roughly 7 usable in each — and every composite gadget function claims some. Both directions are tracked, though in practice IN is the binding constraint:
The full default set + NCM = 8 IN (over the ~7 budget) while OUT stays well under, so IN is what overflows. When the controller runs out, NCM's bulk-IN silently fails to allocate: the interface enumerates and
usb0comes up (RX works), but TX is a black hole. This was a genuinely painful thing to debug while rebasing onto the new audio gadget, so:internal/usbgadget/endpoints.gomodels per-function IN and OUT cost against independent IN/OUT budgets (constants, with the empirical derivation documented).getUsbEndpointReportRPC returns{exceedsBudget}— a single boolean — for any device selection. The UI only needs to know whether the limit is hit, not the exact count.Note: this branch is rebased onto current
dev, which now includes the USB audio gadget; the budget math accounts for it.Out of scope (deferred to follow-ups)
nmliteintegration;usb0is independent ofeth0.Checklist
make test_e2elocally and passed🤖 Generated with Claude Code