detached 9b65185

ci(debug): probe raw DNS answers from the sandbox forwarder

commit
9b651856f3edafe9b288efd7e3dd88d2e5a9a36c
author
brice · · 2026-10-21 12:22 UTC
parent
10fa005
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
checks
1/1
1 file · +51 -20
M .github/workflows/debug-dns.yml +51 -20
@@ -7,24 +7,55 @@ jobs:
77 dns:
88 runs-on: ubuntu-latest
99 steps:
10- - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
11- with:
12- node-version: '24'
1310 - run: |
14- set -x
15- cat /etc/resolv.conf
16- cat /proc/sys/net/ipv6/conf/all/disable_ipv6 || true
17- ip -6 addr || true
18- getent ahostsv4 storage.googleapis.com || true
19- getent ahostsv6 storage.googleapis.com || true
20- getent ahostsv4 cdn.playwright.dev || true
21- getent ahostsv6 cdn.playwright.dev || true
22- node -e '
23- const dns=require("dns");
24- for (const h of ["cdn.playwright.dev","storage.googleapis.com"])
25- for (const family of [4,6])
26- dns.promises.lookup(h,{all:true,family}).then(
27- r=>console.log(h,family,"OK",JSON.stringify(r)),
28- e=>console.log(h,family,"ERR",e.code));
29- '
30- curl -4 -sS -o /dev/null -w '%{http_code} %{remote_ip}\n' -L --max-time 30 -r 0-1023 https://cdn.playwright.dev/builds/cft/156.0.8078.4/linux64/chrome-linux64.zip || true
11+ cat > /tmp/probe.py <<'EOF'
12+ import random, socket, struct, time
13+
14+ NS = ("10.200.0.1", 53)
15+
16+ def query(name, qtype, edns):
17+ qid = random.randint(0, 65535)
18+ q = struct.pack(">HHHHHH", qid, 0x0100, 1, 0, 0, 1 if edns else 0)
19+ for label in name.split("."):
20+ q += bytes([len(label)]) + label.encode()
21+ q += b"\x00" + struct.pack(">HH", qtype, 1)
22+ if edns:
23+ q += b"\x00" + struct.pack(">HHIH", 41, edns, 0, 0)
24+ return q
25+
26+ def show(tag, r, t):
27+ flags, an = struct.unpack(">H", r[2:4])[0], struct.unpack(">H", r[6:8])[0]
28+ print(f"{tag}: {len(r)} bytes, rcode={flags & 0xF}, tc={(flags >> 9) & 1}, answers={an}, {t:.0f} ms")
29+
30+ def udp(name, qtype, edns):
31+ s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
32+ s.settimeout(4)
33+ t = time.time()
34+ s.sendto(query(name, qtype, edns), NS)
35+ try:
36+ show(f"udp {name} type={qtype} edns={edns}", s.recv(65535), (time.time() - t) * 1000)
37+ except Exception as e:
38+ print(f"udp {name} type={qtype} edns={edns}: {e!r}")
39+
40+ def tcp(name, qtype):
41+ q = query(name, qtype, 0)
42+ s = socket.create_connection(NS, timeout=4)
43+ t = time.time()
44+ s.sendall(struct.pack(">H", len(q)) + q)
45+ try:
46+ n = struct.unpack(">H", s.recv(2))[0]
47+ r = b""
48+ while len(r) < n:
49+ r += s.recv(n - len(r))
50+ show(f"tcp {name} type={qtype}", r, (time.time() - t) * 1000)
51+ except Exception as e:
52+ print(f"tcp {name} type={qtype}: {e!r}")
53+
54+ for name in ["storage.googleapis.com", "cdn.playwright.dev", "github.com"]:
55+ for qtype in (1, 28):
56+ for edns in (0, 1232):
57+ udp(name, qtype, edns)
58+ tcp(name, 1)
59+ EOF
60+ python3 /tmp/probe.py
61+ for i in 1 2 3; do getent ahostsv4 storage.googleapis.com | head -1 || echo "getent failed"; done
@@ -7,24 +7,55 @@ jobs:
7 dns:7 dns:
8 runs-on: ubuntu-latest8 runs-on: ubuntu-latest
9 steps:9 steps:
10- - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
11- with:
12- node-version: '24'
13 - run: |10 - run: |
14- set -x11+ cat > /tmp/probe.py <<'EOF'
15- cat /etc/resolv.conf12+ import random, socket, struct, time
16- cat /proc/sys/net/ipv6/conf/all/disable_ipv6 || true13+
17- ip -6 addr || true14+ NS = ("10.200.0.1", 53)
18- getent ahostsv4 storage.googleapis.com || true15+
19- getent ahostsv6 storage.googleapis.com || true16+ def query(name, qtype, edns):
20- getent ahostsv4 cdn.playwright.dev || true17+ qid = random.randint(0, 65535)
21- getent ahostsv6 cdn.playwright.dev || true18+ q = struct.pack(">HHHHHH", qid, 0x0100, 1, 0, 0, 1 if edns else 0)
22- node -e '19+ for label in name.split("."):
23- const dns=require("dns");20+ q += bytes([len(label)]) + label.encode()
24- for (const h of ["cdn.playwright.dev","storage.googleapis.com"])21+ q += b"\x00" + struct.pack(">HH", qtype, 1)
25- for (const family of [4,6])22+ if edns:
26- dns.promises.lookup(h,{all:true,family}).then(23+ q += b"\x00" + struct.pack(">HHIH", 41, edns, 0, 0)
27- r=>console.log(h,family,"OK",JSON.stringify(r)),24+ return q
28- e=>console.log(h,family,"ERR",e.code));25+
29- '26+ def show(tag, r, t):
30- curl -4 -sS -o /dev/null -w '%{http_code} %{remote_ip}\n' -L --max-time 30 -r 0-1023 https://cdn.playwright.dev/builds/cft/156.0.8078.4/linux64/chrome-linux64.zip || true27+ flags, an = struct.unpack(">H", r[2:4])[0], struct.unpack(">H", r[6:8])[0]
28+ print(f"{tag}: {len(r)} bytes, rcode={flags & 0xF}, tc={(flags >> 9) & 1}, answers={an}, {t:.0f} ms")
29+
30+ def udp(name, qtype, edns):
31+ s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
32+ s.settimeout(4)
33+ t = time.time()
34+ s.sendto(query(name, qtype, edns), NS)
35+ try:
36+ show(f"udp {name} type={qtype} edns={edns}", s.recv(65535), (time.time() - t) * 1000)
37+ except Exception as e:
38+ print(f"udp {name} type={qtype} edns={edns}: {e!r}")
39+
40+ def tcp(name, qtype):
41+ q = query(name, qtype, 0)
42+ s = socket.create_connection(NS, timeout=4)
43+ t = time.time()
44+ s.sendall(struct.pack(">H", len(q)) + q)
45+ try:
46+ n = struct.unpack(">H", s.recv(2))[0]
47+ r = b""
48+ while len(r) < n:
49+ r += s.recv(n - len(r))
50+ show(f"tcp {name} type={qtype}", r, (time.time() - t) * 1000)
51+ except Exception as e:
52+ print(f"tcp {name} type={qtype}: {e!r}")
53+
54+ for name in ["storage.googleapis.com", "cdn.playwright.dev", "github.com"]:
55+ for qtype in (1, 28):
56+ for edns in (0, 1232):
57+ udp(name, qtype, edns)
58+ tcp(name, 1)
59+ EOF
60+ python3 /tmp/probe.py
61+ for i in 1 2 3; do getent ahostsv4 storage.googleapis.com | head -1 || echo "getent failed"; done

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 2

Tags 13

↑↓ moveenter openesc close