- pxe-nic-check: count 802.1X only from an authenticator (EAPOL type 0 with EAP Request/Success/Failure). iPXE sends EAPOL-Start on every NIC open and Windows sends Start/Response, so a PXE-booting bay made the verdict 'ge' and the watch stopped dnsmasq for the floor. - pxe-nic-check: join the STP/PAE/LLDP/CDP/PVST+ multicast groups on every capture socket, so CDP-only ports are heard on a plain NIC. - pxe-nic-check --watch: if dnsmasq was (re)started inside the window, re-check (~6 s) before stopping it; skip when already stopped. New --state-cmd and --print-imaging. - pxe-nic-carrier (+ pxe-nic-carrier.service): networkd-dispatcher hook for the imaging NIC only. Carrier loss stops dnsmasq; carrier gain restarts it through its guard if it was running or the hook stopped it. - pxe-dhcp-hook.sh: clear only SMB (tcp 445/139) conntrack entries and sockets, not all traffic for the client, which raced iPXE's first HTTP fetch. - GetPxeScript.aspx: every boot line has a failure branch (iPXE 2.0 accepts an unsigned EFI kernel and fails at boot under Secure Boot, which aborted the script). - Tests: client EAPOL, packet_mreq packing, watch race, shell helpers on stub PATH, lab scenarios eapol-client, br-eapol-client, race, carrier.
88 lines
3.0 KiB
Python
Executable File
88 lines
3.0 KiB
Python
Executable File
#!/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
|
|
--eapol-start send EAPOL-Start + EAP-Response like a PXE-booting
|
|
client (iPXE, Windows dot3svc) 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("--eapol-start", 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))
|
|
if args.eapol_start:
|
|
s.send(F.eapol_start(mac))
|
|
s.send(F.eapol(mac, code=2))
|
|
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()
|