Files
rollcall/docs/upstream/dnsmasq-proxy-busy-loop/repro.sh
T
Ilker ManapandClaude Opus 5.5 16f76955ac
CI / agent (push) Successful in 3s
CI / site (push) Successful in 2s
CI / go (push) Successful in 36s
Upstream report for the dnsmasq proxy-DHCP busy loop (report, reproducer, patch)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 10:10:25 +01:00

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"