The terminal broadcasts an RFC 951 BOOTP request at power-up. Add an opt-in responder that answers it with yiaddr 192.168.99.2 and siaddr 192.168.99.1, echoing htype/hlen/xid/chaddr. It offers no boot file: the request's sname and file are empty, so the reply's are too. Off by default -- a bare 'serve.py 192.168.99.1' behaves exactly as before (TFTP on port 69, nothing else). Enable with --bootp. Delivery note: the host IP stack never receives the source-0.0.0.0 broadcast, so the responder attaches to the MAME tap's L2 (AF_PACKET) and writes the reply frame back. See harness/BOOTP_REPORT.md for the evidence and the still-open 'User settings invalid' / 'Disk full' symptoms.
5.2 KiB
BOOTP responder for the VT1200 (harness/serve.py --bootp)
Date: 2026-10-16. Scope: harness/serve.py only (a new optional responder).
harness/mame/vt1200.cpp was read, not touched; the ROM is byte-identical
(md5 b46a3e442ccb86134d2356bd70d22fd2).
What was added
serve.py can now answer the BOOTP request the terminal broadcasts at power-up,
so its IP address no longer has to be typed in at the Terminal Manager.
sudo python3 -u harness/serve.py 192.168.99.1 --bootp
--bootp is off by default. A bare serve.py 192.168.99.1 behaves exactly
as before: TFTP font serving on port 69 and nothing else (no port 67 socket, no
AF_PACKET socket, no banner). Extra options: --bootp-client (default
192.168.99.2), --bootp-server (default the TFTP host), --bootp-iface
(default: auto-detect tap-mess-*).
The request, as the terminal actually sends it
Captured on the tap (tcpdump -i tap-mess-1000-0 'udp port 67') while MAME ran
run.sh net headless. It is a plain RFC 951 request with no DHCP extensions:
op=1 htype=1 hlen=6 hops=0
xid=0xf81fccff secs=0,1024,2048,... flags=0x0000
ciaddr=0 yiaddr=0 siaddr=0 giaddr=0
chaddr = 08:00:2b:16:4b:5a:c1:86 00 00 00 00 00 00 00 00 (8 bytes!)
sname = (empty) file = (empty)
vend = 64 zero bytes (no 0x63825363 magic cookie)
Two things worth noting. The request is sent from source 0.0.0.0, UDP port 68
to 255.255.255.255:67, and chaddr carries eight bytes: the six-byte MAC
plus the two checksum bytes the firmware read from its Ethernet address ROM. The
htype/hlen (1/6) say six bytes are meaningful.
It asked for no boot file. sname and file are empty, so the reply leaves
them empty too. No filename was invented.
The reply
op=2, the htype/hlen/xid/secs/flags/chaddr echoed, yiaddr = the
client address, siaddr = the server address, ciaddr and giaddr zero,
sname/file empty, 300 bytes total (236-byte header + zero vend).
Why it is an L2 responder (AF_PACKET), not a plain UDP socket
The host IP stack never receives the request. The terminal sends it from
0.0.0.0 into the MAME tap, which is enslaved to the vt1200 bridge. With a UDP
socket bound to 0.0.0.0:67 on the host, no datagram arrives (verified: a
listener saw zero, and net.ipv4.conf.vt1200.rp_filter=0 made no difference).
The frame is, however, visible at the tap's L2 — which is where tcpdump sees
it — and an AF_PACKET socket bound to tap-mess-1000-0 does receive it. The
responder therefore reads the tap at L2 and writes the reply frame back. This is
a delivery choice; the packet is still an RFC 951 reply. (ETH_P_ALL is used
because ETH_P_IP misses the bridged-in frames.)
The reply is a broadcast: ff:ff:ff:ff:ff:ff, source MAC = the bridge's MAC
(c6:fc:cc:16:f5:66), IP 192.168.99.1 → 255.255.255.255, UDP 67 → 68.
Evidence the terminal adopted 192.168.99.2
Server log (--bootp):
bootp: L2 responder on tap-mess-1000-0: handing out 192.168.99.2,
siaddr 192.168.99.1 (src mac c6:fc:cc:16:f5:66)
bootp: request from 08:00:2b:16:4b:5a xid=f81fccff -> 192.168.99.2
tcpdump on the tap (abridged):
08:00:2b:16:4b:5a > ff:ff:ff:ff:ff:ff 255.255.255.255.68 > 255.255.255.255.67 BOOTP Request
c6:fc:cc:16:f5:66 > ff:ff:ff:ff:ff:ff 192.168.99.1.67 > 255.255.255.255.68 BOOTP Reply Your-IP 192.168.99.2
08:00:2b:16:4b:5a > ff:ff:ff:ff:ff:ff 192.168.99.2.1024 > 192.168.99.255.771 UDP, payload "RCP\0DEC VT1000\0..."
The only one request (previously five retries over ~6 s) is itself the reply
being accepted; and the next frame is sourced from 192.168.99.2.
The terminal's own screen (frame dump, VT1200_DUMP_FRAMES), Terminal Manager:
User settings invalid.
Using local fonts.
System defaults loaded.
IP address set to 192.168.99.2 IP subnet mask set to 255.255.255.0.
TCP/IP is now up.
Ethernet Address 08-00-2B-16-4B-5A IP Address 192.168.99.2
The two live symptoms
-
"User settings invalid." / NVRAM does not persist — STILL PRESENT, not made moot by BOOTP. BOOTP fixes only the address: the terminal now prints
IP address set to 192.168.99.2 ... TCP/IP is now upinstead ofCould not determine IP address after 5 retries / TCP/IP is now down. TheUser settings invalid. / System defaults loaded.banner remains, and the emulated NVRAM file (~/.mame/nvram/vt1200/nvram) is written back all zeroes — a storage problem, and the driver holding it is out of scope here. -
Font fetch aborting with TFTP error 3 ("Disk full or allocation exceeded") — NOT REPRODUCED on this boot, and unrelated to BOOTP. With settings invalid the terminal prints
Using local fonts.and never configures a TFTP font path, so it issues no RRQ at all in a fresh run; the server log shows no TFTP request. The abort is a client-side condition (the terminal itself repliesERROR ENOSPACE), which a server-side change cannot cause or cure — as the task preamble anticipated. Reproducing it needs a configured TFTP font path, which needs the settings NVRAM to persist first.
Default behaviour
A bare serve.py 192.168.99.1 was run and checked: it prints only the TFTP
banner, listens on 192.168.99.1:69, and opens no port-67 / AF_PACKET socket.
Unchanged.