# USB Ethernet and TFTP on the station
A USB Ethernet adapter granted to this machine over WebUSB puts it on your
local network. With it the machine can fetch files from a TFTP server on that
network, and serve files to a board or a host over TFTP.
Measured on our bench on 2026-10-06, in Chrome on a Linux host, with a TP-Link
USB 10/100 LAN cabled to the bench LAN. Not yet tried: a board's U-Boot fetching
its kernel from this machine, an adapter granted from a Mac, and any other
adapter.
## 1. What you need
- **Developer mode on** for this machine (its Settings). The site then runs the
HardwareJS in this image's `/boot`, which handles the adapter. The site's own
release refused it when last measured (2026-09-30), with `bad CDC
descriptors`.
- **A kernel with the USB Ethernet drivers.** Check with
`ls /sys/bus/usb/drivers/cdc_ether`; if the directory is not there, this
image's kernel does not have them yet.
- **An adapter whose driver is built in:** CDC-ECM (`cdc_ether`), CDC-NCM
(`cdc_ncm`), DM9601 (`dm9601`) or RTL8153 in ECM mode (`r8153_ecm`). The one
we tried is the TP-Link USB 10/100 LAN (`2357:0602`), which binds through
`cdc_ether`. Adapters that need `r8152`, `asix` or `ax88179_178a` do not bind
(`linux#137`).
- **A cable to your network**, and a free static address on it: DHCP does not
work in the browser (section 6).
## 2. Let the browser have the adapter (Linux host)
The host's own kernel binds the adapter as a network interface, and Chrome then
cannot claim it. As root, once:
# /etc/udev/rules.d/70-brintos-webusb.rules
SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ATTRS{idVendor}=="2357", ATTRS{idProduct}=="0602", MODE="0660", GROUP="plugdev", TAG+="uaccess"
ACTION=="bind", SUBSYSTEM=="usb", DRIVER=="cdc_ether", ATTRS{idVendor}=="2357", ATTRS{idProduct}=="0602", RUN+="/bin/sh -c 'echo -n %k > /sys/bus/usb/drivers/cdc_ether/unbind'"
`udevadm control --reload`, then replug. Use your adapter's IDs from `lsusb`.
If `lsusb -t` shows the host bound it with another driver (`r8152`, say),
unbind that one instead. The host loses the adapter as its own interface while
the rule is installed. On a Mac this has not been tried.
## 3. Attach it and bring it up
In the machine's **USB** tab, **Attach USB device**, pick the adapter. Wait about
ten seconds, go back to the **Console** tab, then:
lsusb # the adapter's ID
dmesg | grep -E 'cdc_ether|cdc_ncm|dm9601' | tail -n 3
ip link set eth1 up
ip addr add
/24 dev eth1
ip route get # must name eth1
`eth0` is the machine's own virtual network card, which the site provides and
relays through the browser. It is not a path to your local network, so leave it
alone and put the address on the adapter. If the adapter is not `eth1`, find it
by its MAC address:
grep -l /sys/class/net/*/address
The carrier shows a few seconds after `ip link set … up`:
read c < /sys/class/net/eth1/carrier; echo $c # 1 with the cable in
## 4. Fetch a file (TFTP client)
tftp -m binary -c get /tmp/
curl --tftp-blksize 8192 -o /tmp/ tftp:///
- `-m binary` matters: `tftp`'s default mode is netascii, which is for text.
- `curl` is the faster one, because it asks for larger blocks and the cost
through WebUSB is per block (section 7). `tftp` here cannot ask.
- Use IP addresses. Nothing configures a name server on the adapter.
- **Fetch into `/tmp`.** It is the only memory-backed place for files on a
running machine; everything else, `/var/tmp` included, is the machine's own
filesystem, which is slow to write (section 7). Files in `/tmp` are gone after
a restart and are not part of a snapshot.
Check what arrived with `sha256sum /tmp/`.
## 5. Serve files (TFTP server)
The server is `in.tftpd-hpa`. It serves `/var/lib/tftpboot`, and reads its
options from `/etc/default/tftpd-hpa`.
Writing a large file into `/var/lib/tftpboot` is slow: it is the machine's own
filesystem, about 17 kB/s, and the 5.8 MB kernel took 337 s. So keep the file
in `/tmp` and put a link to it in `/var/lib/tftpboot`:
curl --tftp-blksize 8192 -o /tmp/ tftp:/// # or any way into /tmp
ln -s /tmp/ /var/lib/tftpboot/
/etc/init.d/tftpd-hpa start
grep ':0045 00000000:0000' /proc/net/udp # one line: listening on port 69
A file the image already carries is linked the same way, with no copy at all:
ln -s /usr/share/brintos-som/stm32f7/u-boot.bin /var/lib/tftpboot/u-boot.bin
Then, from a host or a board on the network, fetch `` from this machine's
address, e.g. on a Linux host:
curl -o tftp:///
- Serving through a link was checked across the cable: a host on the LAN
fetched U-Boot linked from `/tmp` byte-for-byte, in 6 s.
- After a restart the links are still there and `/tmp` is empty: fetch the
files into `/tmp` again.
- The options are `-l -u root -m /etc/tftpd-hpa.remap /var/lib/tftpboot`. There
is no `-s`, because its per-request `chroot()` fails in the browser
(`glibc#14`); the remap rule gives a name without a leading `/` the same
directory instead. `-u root` is there because this kernel cannot drop
privileges (`brintos/linux#19`).
- inetutils' `in.tftpd` is on the image too, but it serves only under an inetd,
and inetd here handles one request and then stops (`brintos/linux#21`).
## 6. What does not work
- **DHCP.** With packet sockets in the kernel, `udhcpc` never returned in the
browser, so they are left out and a DHCP client fails at once (`linux#81`).
Give the adapter a static address.
- **`eth0` to your local network** — see section 3.
- **`cat` of a file may print `glibc-wasm32: RESTART-SENTINEL-LEAK val=517 nr=40
…` instead of the file** (`glibc#14`). Read it with `sed -n p `.
- **Not tried:** `ping` from the machine, a board's U-Boot fetching from the
machine, two adapters at once.
- **Copying with `cp`** may hit the same `glibc#14` leak as `cat`; `dd` and `ln -s`
do not use the call it leaks from.
## 7. How fast it is
The board's 5.8 MB kernel (`rootfs.uImage`), from a TFTP server on the same
LAN. "Host" is the Linux PC running Chrome; on its own it fetches the file in
about a second.
| Transfer | Block size | Time | Throughput |
|---|---|---|---|
| this machine ← server, `tftp` | 512 B (default) | 123–132 s | 47 kB/s |
| this machine ← server, `curl --tftp-blksize` | 1,468 B | 35 s | 164 kB/s |
| this machine ← server, `curl --tftp-blksize` | 8,192 B | 11 s | 517 kB/s |
| host ← this machine's server | 512 B (default) | 75–84 s | 70 kB/s |
| host ← this machine's server | 1,468 B | 29 s | 198 kB/s |
| host ← this machine's server | 8,192 B | 9.4 s | 615 kB/s |
The first transfer is no slower than the next. The cost is per TFTP block,
about 7 to 16 ms each through WebUSB, so larger blocks are what makes it fast.
Writing into the machine's own filesystem runs at about 17 kB/s.