ci(debug): probe raw DNS answers from the sandbox forwarder
1 file · +51 -20
M .github/workflows/debug-dns.yml +51 -20
| @@ -7,24 +7,55 @@ jobs: | |||
| 7 | 7 | dns: | |
| 8 | 8 | runs-on: ubuntu-latest | |
| 9 | 9 | steps: | |
| 10 | - | - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 | |
| 11 | - | with: | |
| 12 | - | node-version: '24' | |
| 13 | 10 | - 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-latest | 8 | 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 -x | 11 | + | cat > /tmp/probe.py <<'EOF' |
| 15 | - | cat /etc/resolv.conf | 12 | + | import random, socket, struct, time |
| 16 | - | cat /proc/sys/net/ipv6/conf/all/disable_ipv6 || true | 13 | + | |
| 17 | - | ip -6 addr || true | 14 | + | NS = ("10.200.0.1", 53) |
| 18 | - | getent ahostsv4 storage.googleapis.com || true | 15 | + | |
| 19 | - | getent ahostsv6 storage.googleapis.com || true | 16 | + | def query(name, qtype, edns): |
| 20 | - | getent ahostsv4 cdn.playwright.dev || true | 17 | + | qid = random.randint(0, 65535) |
| 21 | - | getent ahostsv6 cdn.playwright.dev || true | 18 | + | 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 || true | 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 | |||