Files
vt1200/harness/BOOTP_REPORT.md
SDD 264d5330ad harness/serve.py: optional BOOTP responder (--bootp) so the VT1200 gets an IP at boot
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.
2026-10-16 17:27:00 +03:00

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

  1. "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 up instead of Could not determine IP address after 5 retries / TCP/IP is now down. The User 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.

  2. 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 replies ERROR 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.