#!/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()