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:
50
tests/pxe-nic-check/README.md
Normal file
50
tests/pxe-nic-check/README.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# pxe-nic-check tests
|
||||
|
||||
Tests for `playbook/pxe-server-helpers/pxe-nic-check`. Neither needs root
|
||||
or touches a real network.
|
||||
|
||||
## Unit tests
|
||||
|
||||
python3 -m unittest discover -s tests/pxe-nic-check -v
|
||||
|
||||
Synthesized LLDP, CDP, EAPOL, STP and ARP frames and DHCPOFFER/ACK packets
|
||||
(`frames.py`) fed to the parsers; the verdict table; own-server and
|
||||
cloned-PXE-server handling; networkd lease parsing; config fallbacks; the
|
||||
dnsmasq_bound logic; the status JSON shape and atomic 0644 write.
|
||||
|
||||
## Namespace lab
|
||||
|
||||
python3 tests/pxe-nic-check/lab.py # all scenarios, ~5 min
|
||||
python3 tests/pxe-nic-check/lab.py rogue br-lldp # some
|
||||
|
||||
Re-runs itself under `unshare --user --map-root-user --net --mount`: a
|
||||
private network namespace with veth pairs, so raw sockets work without sudo
|
||||
and every frame stays inside it. Each scenario runs the real script in
|
||||
check (`--json --probe`), `--guard` and `--watch` modes (watch with
|
||||
`--stop-cmd "touch <marker>"` instead of `systemctl stop dnsmasq`) and
|
||||
asserts the imaging verdict, the guard exit code, whether watch "stopped
|
||||
dnsmasq", the evidence text and the status JSON shape.
|
||||
|
||||
Peers: `lab_peer.py` (a raw-socket DHCP responder and LLDP/EAPOL sender;
|
||||
refuses to run unless `PXE_NIC_LAB=1`, which only the lab sets) and real
|
||||
`dnsmasq` if installed (started with `--user= --group=`, since a user
|
||||
namespace cannot drop privileges; scenarios are skipped without it).
|
||||
|
||||
| Scenario | Peer | Expect | Guard |
|
||||
|-----------------|-----------------------------------------------|---------------|-------|
|
||||
| isolated | none | isolated | 0 |
|
||||
| rogue | DHCP, no domain | foreign-dhcp | 1 |
|
||||
| fake-ge | DHCP domain test.geaerospace.net + LLDP | ge | 1 |
|
||||
| not-corp-domain | DHCP domain notgeaerospace.net | foreign-dhcp | 1 |
|
||||
| lldp-only | LLDP | ge | 1 |
|
||||
| eapol-only | 802.1X EAPOL | ge | 1 |
|
||||
| own-server | DHCP with server id 172.16.9.1 from a local MAC | isolated | 0 |
|
||||
| cloned-pxe | DHCP with server id 172.16.9.1, foreign MAC | foreign-dhcp | 1 |
|
||||
| no-link | peer end down | no-link | 0 |
|
||||
| dnsmasq-corp | real dnsmasq, domain lab.geaerospace.net | ge | 1 |
|
||||
| dnsmasq-rogue | real dnsmasq, no domain | foreign-dhcp | 1 |
|
||||
| own-dnsmasq | real dnsmasq serving the imaging NIC itself | isolated, dnsmasq_bound true | 0 |
|
||||
| br-* | same, imaging NIC is a bridge (like br-pxe), named in nic.conf by a port's MAC | | |
|
||||
|
||||
The GE NIC (g0) is checked from a fake networkd lease with domain
|
||||
test.geaerospace.net and must read `ge` in every scenario.
|
||||
Reference in New Issue
Block a user