Files
vt1200/harness/BOOTP_REPORT.md
T
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

120 lines
5.2 KiB
Markdown

# 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.