7b94da0be5
The trace settled it: our reply satisfies EVERY check fsplib makes -- fsp_pkt_read's checksum (0x9a computed, 0x9a present), len <= recv_len, (reply.seq & 0xfff8) == s->seq, cmd match, and fsp_getpro's pos == FSP_PRO_BYTES with the flags byte at buf[len]. Yet the console retried the same transaction (seq 0x8ccb -> 0x8ccc, txn 0x8cc8 try 3 -> try 4). The decisive evidence is the key: fsplib calls client_set_key(rpkt->key) on any ACCEPTED reply, so the next request would carry our 0x1234. Every request still carries 0x8022. No reply has ever been accepted -- so they are not arriving, and the problem was never the protocol. What differs from the thing that worked: mini-dhcpd.py replies over AF_PACKET and got a DHCP lease immediately, while this server used a normal UDP socket, whose reply depends on the kernel having a route to the console, an ARP entry for it, and choosing the right source address. (AF_PACKET also never populates the kernel ARP table, so DHCP succeeding tells us nothing about those.) --iface <dev> now replies with raw ethernet frames that MIRROR the request's own addresses -- MACs swapped, IPs swapped, ports swapped -- which removes routing, ARP and source-address selection in one go. The UDP path is kept for when it is not needed. Frame construction is tested: the reply carries the console's MAC as destination and ours as source, source IP 10.0.0.1 and destination 10.0.0.64 taken from the request, ports 21 -> 4096, a valid IP checksum, correct UDP length, and padding to the 60-byte ethernet minimum. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>