58 lines
2.4 KiB
Bash
Executable File
58 lines
2.4 KiB
Bash
Executable File
#!/bin/sh
|
|
# dnsmasq proxy-DHCP busy loop reproducer. Run as root on Linux.
|
|
# ./repro.sh [/path/to/dnsmasq]
|
|
# Creates two network namespaces joined by a veth pair, runs dnsmasq with one
|
|
# tagged proxy range in one, sends two PXE DHCPDISCOVERs from the other, then
|
|
# reports dnsmasq's CPU use. A healthy dnsmasq stays near 0%.
|
|
set -eu
|
|
DNSMASQ=${1:-dnsmasq}
|
|
D=$(mktemp -d)
|
|
cleanup() { [ -f "$D/pid" ] && kill "$(cat "$D/pid")" 2>/dev/null || true; ip netns del dq-srv 2>/dev/null || true; ip netns del dq-cli 2>/dev/null || true; rm -rf "$D"; }
|
|
trap cleanup EXIT
|
|
cleanup 2>/dev/null || true; D=$(mktemp -d)
|
|
ip netns add dq-srv; ip netns add dq-cli
|
|
ip link add dq0 type veth peer name dq1
|
|
ip link set dq0 netns dq-srv; ip link set dq1 netns dq-cli
|
|
ip -n dq-srv addr add 192.0.2.1/24 dev dq0; ip -n dq-srv link set dq0 up; ip -n dq-srv link set lo up
|
|
ip -n dq-cli link set dq1 up; ip -n dq-cli link set lo up
|
|
mkdir -p "$D/tftp"
|
|
cat > "$D/dnsmasq.conf" <<EOF
|
|
port=0
|
|
interface=dq0
|
|
bind-interfaces
|
|
log-dhcp
|
|
enable-tftp
|
|
tftp-root=$D/tftp
|
|
dhcp-range=set:prov,192.0.2.0,proxy,255.255.255.0
|
|
pxe-service=tag:prov,x86PC,"boot",pxelinux
|
|
EOF
|
|
ip netns exec dq-srv "$DNSMASQ" --conf-file="$D/dnsmasq.conf" --pid-file="$D/pid" --log-facility="$D/log" -u root
|
|
sleep 1
|
|
"$DNSMASQ" --version | head -1
|
|
# two PXE DISCOVERs (vendor class PXEClient, client arch 0 = x86 BIOS), like a PXE/iPXE ROM sends
|
|
ip netns exec dq-cli python3 - <<'P'
|
|
import os, socket, struct, time
|
|
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
|
s.setsockopt(socket.SOL_SOCKET, 25, b"dq1") # SO_BINDTODEVICE
|
|
s.bind(("0.0.0.0", 68))
|
|
mac = bytes.fromhex("020000000001")
|
|
for n in range(2):
|
|
xid = os.urandom(4)
|
|
p = bytearray(240)
|
|
p[0:4] = bytes([1, 1, 6, 0]); p[4:8] = xid
|
|
struct.pack_into(">H", p, 10, 0x8000)
|
|
p[28:34] = mac; p[236:240] = bytes([99, 130, 83, 99])
|
|
vc = b"PXEClient:Arch:00000:UNDI:002001"
|
|
p += bytes([53, 1, 1, 60, len(vc)]) + vc + bytes([93, 2, 0, 0, 94, 3, 1, 2, 1, 97, 17, 0]) + os.urandom(16) + bytes([255])
|
|
s.sendto(bytes(p), ("255.255.255.255", 67))
|
|
time.sleep(1)
|
|
P
|
|
sleep 2
|
|
P=$(cat "$D/pid")
|
|
a=$(awk '{print $14+$15}' /proc/$P/stat); sleep 5; b=$(awk '{print $14+$15}' /proc/$P/stat)
|
|
cpu=$(( (b - a) * 100 / (5 * $(getconf CLK_TCK)) ))
|
|
echo "dnsmasq CPU over 5 s: ${cpu}%"
|
|
grep -c "proxy" "$D/log" | sed 's/^/proxy replies logged: /'
|
|
[ "$cpu" -lt 50 ] && echo "RESULT: ok" || echo "RESULT: BUSY LOOP"
|