import importlib.util, socket, struct, binascii, sys spec = importlib.util.spec_from_file_location("d", "/workspace/scripts/mini-dhcpd.py") d = importlib.util.module_from_spec(spec); spec.loader.exec_module(d) GC = bytes.fromhex("0009bf000001") SRV = bytes.fromhex("aabbccddeeff") fails = [] def discover(magic=d.MAGIC, dport=67, etype=d.ETH_P_IP): bootp = struct.pack("!BBBB4sHH", 1, 1, 6, 0, b"\xde\xad\xbe\xef", 0, 0x8000) bootp += socket.inet_aton("0.0.0.0") * 4 bootp += GC + b"\0" * 10 + b"\0" * 64 + b"\0" * 128 + magic bootp += bytes([53, 1, 1]) + bytes([55, 3, 1, 3, 6]) + b"\xff" udp = struct.pack("!HHHH", 68, dport, 8 + len(bootp), 0) + bootp ip = struct.pack("!BBHHHBBH4s4s", 0x45, 0, 20 + len(udp), 1, 0, 64, 17, 0, socket.inet_aton("0.0.0.0"), socket.inet_aton("255.255.255.255")) ip = ip[:10] + struct.pack("!H", d.ip_csum(ip)) + ip[12:] return GC_frame(etype, ip + udp) def GC_frame(etype, pl): return bytes.fromhex("ffffffffffff") + GC + struct.pack("!H", etype) + pl # T1: a well-formed DISCOVER parses kind, info = d.parse_dhcp(discover(), GC) if kind != "dhcp" or info["msg_type"] != d.DHCP_DISCOVER: fails.append(f"T1 DISCOVER parse: {kind} {info and info.get('msg_type')}") elif info["chaddr"] != GC or info["xid"] != b"\xde\xad\xbe\xef": fails.append(f"T1 fields: chaddr={info['chaddr'].hex()} xid={info['xid'].hex()}") else: print("T1 well-formed DISCOVER parses, chaddr + xid correct") # T2: a corrupted magic cookie is reported as MALFORMED, not ignored. # This is the case that matters: a shifted/corrupt TX frame must be # visible, not silently dropped the way a UDP socket would drop it. kind, why = d.parse_dhcp(discover(magic=b"\x00\x00\x00\x00"), GC) if kind != "malformed": fails.append(f"T2 corrupt cookie reported as {kind}, want malformed") else: print(f"T2 corrupt cookie -> malformed: {why}") # T3: frames from other MACs are ignored kind, _ = d.parse_dhcp(discover(), bytes.fromhex("001122334455")) if kind != "other": fails.append(f"T3 foreign MAC: {kind}") else: print("T3 foreign MAC ignored") # T4: a reply is a valid IPv4/UDP frame with a correct IP checksum bootp = d.build_bootp(d.DHCP_OFFER, b"\xde\xad\xbe\xef", 0x8000, GC, "10.0.0.64", "10.0.0.1", "255.255.255.0", "10.0.0.1", "10.0.0.1", 3600) out = d.build_reply(SRV, GC, "10.0.0.1", "10.0.0.64", bootp) if out[0:6] != GC: fails.append("T4 reply not unicast to the client MAC") ip = out[14:] if d.ip_csum(ip[:20]) != 0: fails.append(f"T4 IP checksum invalid ({d.ip_csum(ip[:20]):#06x}, want 0)") sp, dp, ulen, _ = struct.unpack("!HHHH", ip[20:28]) if (sp, dp) != (67, 68): fails.append(f"T4 ports {sp}->{dp}, want 67->68") if ulen != 8 + len(bootp): fails.append(f"T4 UDP length {ulen}, want {8 + len(bootp)}") if struct.unpack("!H", ip[2:4])[0] != len(ip): fails.append("T4 IP total length disagrees with the frame") if len(bootp) < 300: fails.append(f"T4 BOOTP {len(bootp)} bytes, under the 300 minimum") if not fails: print(f"T4 OFFER: unicast to {d.mac_str(out[0:6])}, IP csum ok, " f"67->68, {len(out)} bytes, BOOTP {len(bootp)}") # T5: the OFFER decodes back to the right yiaddr/options y = ip[28 + 16:28 + 20] if socket.inet_ntoa(y) != "10.0.0.64": fails.append(f"T5 yiaddr {socket.inet_ntoa(y)}, want 10.0.0.64") else: print("T5 yiaddr = 10.0.0.64") # T6: the REAL frame from the console, which the first version of this script # reported as a corrupted TX frame. It is a valid lwIP DHCP-lease ARP probe. # Exact frame from the console paste, as ONE literal: splitting it across # two string literals is how the first version of this test lost a byte and # blamed the parser for decoding tpa as 0.10.0.0. real = bytes.fromhex("ffffffffffff0009bf000001080600010800060400010009bf000001000000000000000000000a000040c0004800000000008031b4b08052fef88031") assert len(real) == 60, len(real) kind, info = d.parse_dhcp(real, GC) if kind != "info": fails.append(f"T6 real ARP probe classified as {kind!r}, want 'info' " f"- must NOT be reported as corrupted") elif "probe" not in info or "10.0.0.64" not in info: fails.append(f"T6 ARP probe description unhelpful: {info}") else: print(f"T6 real console frame -> {info}") # T7: a frame that IS IPv4/UDP->67 but damaged must still be 'malformed', # otherwise fixing T6 would have thrown away the diagnostic. kind, why = d.parse_dhcp(discover(magic=b"\xde\xad\xbe\xef"), GC) if kind != "malformed": fails.append(f"T7 damaged DHCP classified as {kind!r}, want 'malformed'") else: print(f"T7 damaged DHCP still flagged: {why}") # T8: a non-ARP, non-IP ethertype is informational, not alarming kind, info = d.parse_dhcp(GC_frame(0x88b5, b"\x00" * 46), GC) if kind != "info" or "0x88b5" not in info: fails.append(f"T8 netboot ethertype: {kind!r} {info!r}") else: print(f"T8 raw ethertype -> {info}") print("\nFAILURES:\n " + "\n ".join(fails) if fails else "\nAll DHCP script tests passed.") sys.exit(1 if fails else 0)