NIC guard: dnsmasq never serves DHCP/PXE on the GE network
pxe-nic-check classifies each NIC from what it hears: LLDP/CDP/802.1X from managed switches, foreign DHCP offers and their domain (one DHCPDISCOVER on the imaging NIC; the GE NIC's networkd lease is read instead), STP and ARP hosts for information. A kernel BPF filter keeps bulk SMB traffic out of the capture loop; bridge ports are listened on too, since LLDP and EAPOL stop at the port. - dnsmasq drop-in runs --guard as ExecStartPre: exit 1 on a ge or foreign-dhcp imaging verdict, fail open (loudly) on bugs or no link - pxe-nic-check.timer runs --watch every 5 min: stops dnsmasq on a bad imaging verdict, never starts it - /run/pxe-nic-status.json (0644, atomic) for the webapp - playbook installs it all and writes /etc/pxe-server/nic.conf from pxe_imaging_mac, pxe_ge_mac, pxe_corp_domain_suffixes, pxe_nic_allowed_switches - unit tests plus a no-root network-namespace lab (veth pairs, a raw socket DHCP/LLDP peer and real dnsmasq); docs/pxe-nic-guard.md has the operator notes and the two floor tests
This commit is contained in:
81
tests/pxe-nic-check/lab_peer.py
Executable file
81
tests/pxe-nic-check/lab_peer.py
Executable file
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
lab_peer.py - the "other end of the cable" for the namespace lab.
|
||||
|
||||
Runs on one end of a veth pair inside a private network namespace and
|
||||
plays a switch and/or a DHCP server with raw AF_PACKET sockets:
|
||||
|
||||
--dhcp IP answer every DHCPDISCOVER with a DHCPOFFER from IP
|
||||
--server-id IP option 54 to put in the offer (default = --dhcp IP)
|
||||
--domain NAME option 15 in the offer
|
||||
--lldp SYSNAME send an LLDP frame every second
|
||||
--eapol send an EAPOL Request/Identity every second
|
||||
--mac MAC source MAC for the frames (default: the iface's MAC)
|
||||
|
||||
Never run this outside the lab netns: it is a rogue DHCP server.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import select
|
||||
import socket
|
||||
import sys
|
||||
import time
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
import frames as F # noqa: E402
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--iface", required=True)
|
||||
ap.add_argument("--dhcp")
|
||||
ap.add_argument("--server-id")
|
||||
ap.add_argument("--domain")
|
||||
ap.add_argument("--lldp")
|
||||
ap.add_argument("--eapol", action="store_true")
|
||||
ap.add_argument("--mac")
|
||||
ap.add_argument("--duration", type=float, default=120)
|
||||
args = ap.parse_args()
|
||||
|
||||
if os.environ.get("PXE_NIC_LAB") != "1":
|
||||
sys.exit("lab_peer.py: refusing to run outside the lab (PXE_NIC_LAB)")
|
||||
|
||||
with open("/sys/class/net/%s/address" % args.iface) as f:
|
||||
mac = args.mac or f.read().strip()
|
||||
s = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(3))
|
||||
s.bind((args.iface, 0))
|
||||
end = time.monotonic() + args.duration
|
||||
next_beacon = 0.0
|
||||
answered = set()
|
||||
sys.stdout.write("ready\n")
|
||||
sys.stdout.flush()
|
||||
while time.monotonic() < end:
|
||||
now = time.monotonic()
|
||||
if now >= next_beacon:
|
||||
if args.lldp:
|
||||
s.send(F.lldp(mac, system=args.lldp, port="Gi1/0/14",
|
||||
vlan=309))
|
||||
if args.eapol:
|
||||
s.send(F.eapol(mac))
|
||||
next_beacon = now + 1.0
|
||||
r, _, _ = select.select([s], [], [], 0.2)
|
||||
if not r:
|
||||
continue
|
||||
frame, addr = s.recvfrom(65535)
|
||||
if addr[2] == 4 or not args.dhcp: # our own outgoing frames
|
||||
continue
|
||||
d = F.parse_discover(frame)
|
||||
if not d or d[0] in answered:
|
||||
continue
|
||||
answered.add(d[0])
|
||||
xid, chaddr = d
|
||||
s.send(F.dhcp_reply(mac, args.dhcp, xid, chaddr,
|
||||
yiaddr="10.99.0.50", domain=args.domain,
|
||||
server_id=args.server_id))
|
||||
sys.stdout.write("offered xid %08x to %s\n" % (xid, chaddr))
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user