forked from arti/vt1200
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.
120 lines
5.2 KiB
Markdown
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.
|