← gobootd
BOOTBRIDGE(8) System Manager's Manual BOOTBRIDGE(8)

bootbridge — connect a network interface to bootd and emulators over UDP

bootbridge [-c config] -i interface [-listen addr:port] [-peer addr:port ...] [-l2tp addr:port] [-l2tp-peer addr:port ...] [-session id] [-cookie hex] [-counter=bool] [-stats duration] [-ctl path] [-v] [-print] [-X] [-print-iface name]

bootbridge -version

bootbridge bridges an Ethernet interface to UDP, so that bootd(8), simh, QEMU and other emulators can exchange frames with the machines on it. It is the only part of gobootd that needs to capture: it must run as root, with CAP_NET_RAW on Linux, or with access to /dev/bpf* on BSD and macOS.

Frames travel over UDP in one of two formats, on separate listeners that can run at once:

raw
One Ethernet frame per datagram, with no header, as the HECnet bridge, simh (attach xq udp:...) and QEMU (-netdev dgram) send them. There is no authentication, so the raw listener should stay on a loopback address; bootbridge warns about any other address.
L2TPv3
Static L2TPv3 over UDP (RFC 3931) carrying Ethernet, without the control connection, as Linux (ip l2tp) and QEMU (-netdev l2tpv3) speak it. Datagrams with the wrong session or cookie are dropped, so the cookie keeps out anyone who does not know it. It is sent in clear, which is no protection against anyone who can see the traffic.

Every endpoint is a UDP address. Fixed peers always receive frames. Any other address that sends a valid frame becomes a peer for 180 seconds. Unicast frames go to the peer that last sent from their destination address, as in a learning switch; broadcast, multicast and frames for unknown destinations go to every peer. Frames also pass between peers, never back to the peer they came from. Fixed peers are sent a keepalive every 60 seconds.

The options are:

config
Read the settings from an HCL configuration file (see CONFIGURATION). Options given on the command line override it.
interface
The network interface to bridge. Required, here or in the configuration file.
addr:port
The UDP address to exchange raw frames on. The default is 127.0.0.1:4711; an empty string disables the raw listener.
addr:port
A raw peer that always receives frames. Can be given more than once.
addr:port
The UDP address to exchange L2TPv3 frames on. None by default.
addr:port
An L2TPv3 peer that always receives frames. Can be given more than once.
id
The L2TPv3 session ID, the same in both directions. The default is 1.
The L2TPv3 cookie, 8 or 16 hex digits, the same in both directions.
=bool
Whether L2TPv3 frames carry the L2-specific sublayer with a sequence number, as Linux and QEMU do by default. The default is true.
duration
How often to log interface statistics, such as 30s (the default) or 5m; 0 disables them.
path
The Unix socket bootbridgectl(1) connects to. The default is /run/bootbridge/bootbridge.sock (or /var/run/bootbridge/bootbridge.sock where there is no /run, as on macOS), or a socket in XDG_RUNTIME_DIR for a user who cannot write /run; an empty string uses the default and - disables the socket.
Log every forwarding error.
, -p
Print packets as they pass through the bridge.
, -dump
Print packets with a hex and ASCII dump, like tcpdump -X.
Only print packets on one side: udp, lan, all (the default), or an interface name.
Print the version.

When standard input is a terminal, bootbridge reads single keys:

, Space, Enter
Toggle printing packets.
, v, d
Toggle the hex dump.
Cycle the printed side: all, udp, lan.
, l, a
Print udp, lan or all packets.
Print the interface statistics.
Quit.
,?
Show the keys.

The same, and more, works without a terminal: the control socket answers bootbridgectl(1), which lists known peers, prints statistics, shows the frames that pass through, and changes these settings while bootbridge runs.

The configuration file is HCL, the format bootd.hcl(5) describes. Only the listeners it declares are opened: without a raw block there is no raw listener. Its settings are:

= string
The network interface, as -i.
= duration
As -stats.
= string
As -ctl.
= string
The socket's permissions as octal digits, e.g. “0600” for root only; the default is “0660”, so the group of the user running bootbridge can query it. Any local user who can write the socket can see every frame bridged and change what is printed.
= bool
As -v.
= bool
As -print.
= bool
As -X.
As -print-iface.
{...}
The raw listener, with listen (default 127.0.0.1:4711) and peers, a list of fixed peers.
{...}
The L2TPv3 listener, with listen (required), peers, session, cookie and counter.

-peer and -l2tp-peer replace the file's lists of peers. Errors are reported with file and line. A file that holds a cookie should be readable by root only; bootbridge warns if anyone can read it.

/etc/bootbridge.hcl
The configuration file the Debian and Ubuntu service uses.
/usr/local/etc/bootbridge.hcl
The configuration file the macOS launch daemon uses.
/run/bootbridge/bootbridge.sock
The control socket, when bootbridge runs as root.

bootbridge exits 0 when it is stopped, 1 if it cannot open the interface or a listener, and 2 for invalid usage or configuration.

Connect en0 to a bootd(8) listening on 127.0.0.1:4712:

# bootbridge -i en0 -listen 127.0.0.1:4711 -peer 127.0.0.1:4712

Accept L2TPv3 from bootd(8) or QEMU on other machines, which need to know the cookie:

# bootbridge -i en0 -l2tp 0.0.0.0:1701 -cookie 0x0123456789abcdef

The same as a configuration file, with a raw listener for local emulators as well:

interface = "en0"

raw {
  listen = "127.0.0.1:4711"
}

l2tp {
  listen = "0.0.0.0:1701"
  cookie = "0x0123456789abcdef"
}

Connect QEMU on Linux to that L2TPv3 listener on 192.168.1.10:

-netdev l2tpv3,id=n0,udp=on,src=0.0.0.0,srcport=1701,\
    dst=192.168.1.10,dstport=1701,txsession=1,rxsession=1,\
    cookie64=on,txcookie=0x0123456789abcdef,\
    rxcookie=0x0123456789abcdef,counter=on

Watch the frames from another terminal while bootbridge runs as a service:

$ bootbridgectl dump -t 20

pcap(3PCAP), bootd.hcl(5), bootd(8), bootbridgectl(1)

Layer Two Tunneling Protocol - Version 3 (L2TPv3), RFC 3931. Transport of Ethernet Frames over Layer 2 Tunneling Protocol Version 3 (L2TPv3), RFC 4719.

Rico Pajarola <rp@servium.ch>

Full frames exceed a 1500-byte MTU as UDP datagrams and are fragmented across real networks. There is no loop protection: two bridges joined twice pass frames around forever.

Receive offload (GRO on Linux) merges bursts of TCP segments into packets larger than any Ethernet frame before bootbridge captures them. bootbridge splits them again into segments no larger than the MSS the receiving peer announced in its SYN, or 536 bytes if it did not see the SYN. Other oversized frames are dropped and counted as errors.

# ethtool -K enp2s0 gro off
turns the merging off.

Wi-Fi access points drop frames from Ethernet addresses that have not associated, so bridging a Wi-Fi interface does not work for bootd(8) 's own address; use a pcap network in bootd.hcl(5) with mac = "interface" instead.

October 14, 2026 Ubuntu