Repository navigation
Bump hickory-resolver from 0.25.2 to 0.26.2 - #175
Conversation
hickory-resolver 0.26 replaced NameServerConfig::socket_addr with an `ip` field, so get_system_resolvers() reads that instead.
DNS Resolver Benchmark
All metrics
|
en0f
left a comment
There was a problem hiding this comment.
Reviewed at 9e8119c.
🔴 Validation is stated, not shown, and misses the platforms that changed
The Tests paragraph is two claims with no command or output ("fmt and clippy pass" is lint, not tests), and the org git standard (§ Pull requests) asks for the commands run and their real output. CI covers Linux only: every test job runs on ubuntu-24.04. hickory-resolver 0.26 replaced the resolv.conf reader on macOS (SCDynamicStore) and Android (JNI), so get_system_resolvers() now reads from a different source on platforms its docstring promises.
Paste the commands you ran and their output, say which OS they ran on, and link the green CI run. Then either add python -c 'import blastdns; print(blastdns.get_system_resolvers())' on macOS next to scutil --dns, or state plainly that macOS and Android are not validated.
🟢 The one-line migration reads the same addresses on Linux and Windows
0.25.2 always built socket_addr with port 53, so ns.socket_addr.ip() only dropped a fixed port, and 0.26.2 exposes the same address as pub ip: IpAddr. Reading ns.ip keeps the deduplicated bare IPs that client.rs formats, and the pin bump lands in the same commit as the code it breaks, as toolchain.md asks.
Reproduce this review
# save the script below as verify-175.sh, then:
bash verify-175.sh blacklanternsecurity/blastdns 9e8119c8b9d752ba89fc942041024ff262f6956d
# expected: SUMMARY 9 PASS, 5 FAIL, 0 SKIP, 0 BROKEN
# control: bash verify-175.sh blacklanternsecurity/blastdns 00fb035eda8809aaecf790858b29f6237f7c71b7 -> V01=FAIL V02=FAIL V03=FAIL#!/usr/bin/env bash
# verify-175.sh: reproducible checks for blacklanternsecurity/blastdns#175
# Usage: bash verify-175.sh <owner/repo> <sha> [<control-patch>]
# Changes nothing outside a mktemp directory and nothing on GitHub (PR-VF-02).
# Uses only git, grep, awk and sed from the host; no fetched tools to pin.
#
# CONTROL: sha 00fb035eda8809aaecf790858b29f6237f7c71b7
# CONTROL-EXPECT: V01=FAIL V02=FAIL V03=FAIL
set -uo pipefail
REPO="${1:?owner/repo}"; SHA="${2:?sha}"; PATCH="${3:-}"
PASS=0; FAIL=0; SKIP=0; BROKEN=0
pass() { echo "PASS $1 $2"; PASS=$((PASS+1)); }
fail() { echo "FAIL $1 $2 -- $3"; FAIL=$((FAIL+1)); }
skip() { echo "SKIP $1 $2 -- $3"; SKIP=$((SKIP+1)); }
broken() { echo "BROKEN $1 $2 -- $3"; BROKEN=$((BROKEN+1)); }
summary() { echo "SUMMARY $PASS PASS, $FAIL FAIL, $SKIP SKIP, $BROKEN BROKEN"; [ "$FAIL" -eq 0 ] && [ "$BROKEN" -eq 0 ]; }
echo "script_sha256 $(shasum -a 256 "$0" | cut -d' ' -f1)"
echo "git $(git --version | cut -d' ' -f3)"
WORK="$(mktemp -d)"; trap 'rm -rf "$WORK"' EXIT
export GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_NOSYSTEM=1 GIT_TERMINAL_PROMPT=0
GIT=(git -c credential.helper= -c 'credential.helper=!gh auth git-credential')
"${GIT[@]}" clone --quiet --filter=blob:none --no-checkout "https://github.com/$REPO.git" "$WORK/src" || { broken V00 "clone" "clone failed"; summary; exit 1; }
cd "$WORK/src"
"${GIT[@]}" cat-file -e "$SHA^{commit}" 2>/dev/null || "${GIT[@]}" fetch --quiet origin "+refs/pull/*/head:refs/remotes/pr/*" || true
"${GIT[@]}" checkout --quiet --detach "$SHA" || { broken V00 "checkout $SHA" "checkout failed"; summary; exit 1; }
[ "$(git rev-parse HEAD)" = "$SHA" ] || { broken V00 "HEAD is $SHA" "HEAD mismatch"; summary; exit 1; }
if [ -n "$PATCH" ]; then git apply "$PATCH" || { broken V00 "apply control patch" "git apply failed"; summary; exit 1; }; fi
# ---- checks: each line states the desired property --------------------------
# V01 Cargo.toml pins hickory-resolver 0.26.2
line=$(grep -E '^hickory-resolver *=' Cargo.toml || true)
if echo "$line" | grep -q 'version = "0.26.2"'; then pass V01 "Cargo.toml pins hickory-resolver 0.26.2"
else fail V01 "Cargo.toml pins hickory-resolver 0.26.2" "${line:-no hickory-resolver line}"; fi
# V02 Cargo.lock resolves hickory-resolver 0.26.2
lockver() { awk -v n="$1" '$0=="[[package]]"{p=0} $0=="name = \""n"\""{p=1} p&&/^version = /{gsub(/"/,"",$3);print $3;p=0}' Cargo.lock | sort -u | tr '\n' ' ' | sed 's/ $//'; }
rv=$(lockver hickory-resolver)
if [ "$rv" = "0.26.2" ]; then pass V02 "Cargo.lock resolves hickory-resolver 0.26.2"
else fail V02 "Cargo.lock resolves hickory-resolver 0.26.2" "resolved: ${rv:-none}"; fi
# V03 get_system_resolvers() reads the 0.26 NameServerConfig.ip field, not the removed socket_addr
body=$(awk '/^pub fn get_system_resolvers/{p=1} p{print} p&&/^}/{exit}' src/utils.rs)
if echo "$body" | grep -q '\.map(|ns| ns\.ip)' && ! echo "$body" | grep -q 'socket_addr'; then pass V03 "get_system_resolvers reads ns.ip (0.26 API)"
else fail V03 "get_system_resolvers reads ns.ip (0.26 API)" "$(echo "$body" | grep -n 'map(|ns|' | tr -s ' ')"; fi
# V04 the Python boundary test still asserts bare IPs with no port
if awk '/^def test_get_system_resolvers/{p=1} p&&/^def /&&!/test_get_system_resolvers/{exit} p{print}' blastdns/tests/test_client.py | grep -q 'assert ":" not in resolver'; then
pass V04 "test_get_system_resolvers asserts no port at the Python boundary"
else fail V04 "test_get_system_resolvers asserts no port at the Python boundary" "assertion not found"; fi
# V05 hickory_resolver is used only for system_conf in src/utils.rs (no 0.26 type crosses into 0.25 code)
uses=$(grep -rln 'hickory_resolver' --include='*.rs' src | sort | tr '\n' ' ' | sed 's/ $//')
if [ "$uses" = "src/utils.rs" ] && [ "$(grep -c 'hickory_resolver' src/utils.rs)" = "1" ] && grep -q '^use hickory_resolver::system_conf;' src/utils.rs; then
pass V05 "hickory_resolver used only as system_conf in src/utils.rs"
else fail V05 "hickory_resolver used only as system_conf in src/utils.rs" "used in: ${uses:-none}"; fi
# V06 the commit subject is a Conventional Commit (git's own merge commits exempt)
subj=$(git log -1 --format=%s)
if [ "$(git rev-list --parents -n1 HEAD | wc -w | tr -d ' ')" -gt 2 ]; then skip V06 "commit subject is a Conventional Commit" "HEAD is a merge commit: $subj"
elif echo "$subj" | grep -Eq '^(feat|fix|docs|test|refactor|perf|build|ci|chore)(\([^)]+\))?!?: .+'; then pass V06 "commit subject is a Conventional Commit"
else fail V06 "commit subject is a Conventional Commit" "subject: $subj"; fi
# V07 Cargo.lock carries a single hickory-proto version
pv=$(lockver hickory-proto)
if [ "$(echo "$pv" | wc -w | tr -d ' ')" = "1" ]; then pass V07 "Cargo.lock carries one hickory-proto version"
else fail V07 "Cargo.lock carries one hickory-proto version" "versions: $pv"; fi
# ---- added after the round-1 challenge ---------------------------------------
# Upstream crate sources, verified against the checksums in Cargo.lock (base lock for 0.25.2,
# this tree's lock for 0.26.2), with a wrong-hash control.
BASE_SHA=00fb035eda8809aaecf790858b29f6237f7c71b7
lockck() { awk -v n="$1" -v v="$2" '$0=="[[package]]"{p=0;q=0} $0=="name = \""n"\""{p=1} p&&$0=="version = \""v"\""{q=1} p&&q&&/^checksum = /{gsub(/"/,"",$3);print $3;exit}'; }
CR="$WORK/crates"; mkdir -p "$CR"
getcrate() { # name version checksum -> extracts into $CR, returns 0 when the checksum matched
curl -fsSLo "$CR/$1-$2.crate" "https://static.crates.io/crates/$1/$1-$2.crate" || return 2
echo "$3 $CR/$1-$2.crate" | shasum -a 256 -c - >/dev/null 2>&1 || return 3
tar xzf "$CR/$1-$2.crate" -C "$CR" || return 2
}
if [ "$rv" != "0.26.2" ]; then
for id in V08 V09 V10 V11 V13 V14; do skip $id "upstream hickory-resolver comparison" "this tree does not resolve 0.26.2"; done
else
ck25=$(git show "$BASE_SHA:Cargo.lock" | lockck hickory-resolver 0.25.2); ck26=$(lockck hickory-resolver 0.26.2 < Cargo.lock)
getcrate hickory-resolver 0.25.2 "$ck25"; r25=$?; getcrate hickory-resolver 0.26.2 "$ck26"; r26=$?
if echo "0000000000000000000000000000000000000000000000000000000000000000 $CR/hickory-resolver-0.26.2.crate" | shasum -a 256 -c - >/dev/null 2>&1; then
broken V08 "wrong-hash control fails" "accepted a wrong hash"
else pass V08 "wrong-hash control fails"; fi
if [ $r25 -ne 0 ] || [ $r26 -ne 0 ]; then
for id in V09 V10 V11 V13 V14; do skip $id "upstream hickory-resolver comparison" "crate fetch or checksum failed ($r25/$r26)"; done
else
# V09 (A2) 0.26.2 NameServerConfig exposes the address as `pub ip: IpAddr`
if grep -q 'pub ip: IpAddr' "$CR/hickory-resolver-0.26.2/src/config.rs"; then pass V09 "0.26.2 NameServerConfig.ip is an IpAddr"
else fail V09 "0.26.2 NameServerConfig.ip is an IpAddr" "field not found"; fi
# V10 (A2) 0.25.2 always built socket_addr with port 53, so dropping the port loses nothing
if grep -q 'socket_addr: SocketAddr::new(ip.into(), DEFAULT_PORT)' "$CR/hickory-resolver-0.25.2/src/system_conf/unix.rs" && grep -q 'const DEFAULT_PORT: u16 = 53;' "$CR/hickory-resolver-0.25.2/src/system_conf/unix.rs"; then
pass V10 "0.25.2 system_conf always used port 53"
else fail V10 "0.25.2 system_conf always used port 53" "pattern not found"; fi
# V11 (C5/C6) the macOS and Android system_conf backends are unchanged between 0.25.2 and 0.26.2
if grep -q 'target_vendor = "apple"' "$CR/hickory-resolver-0.26.2/src/system_conf/mod.rs" && ! grep -q 'target_vendor = "apple"' "$CR/hickory-resolver-0.25.2/src/system_conf/mod.rs"; then
fail V11 "macOS/Android system_conf backends unchanged by the bump" "0.26.2 adds target_vendor=apple and target_os=android backends; 0.25.2 read resolv.conf on all unix"
else pass V11 "macOS/Android system_conf backends unchanged by the bump"; fi
# V13 (round-2 C18) Windows reads nameserver IPs the same way in both versions (port 53 in 0.25, bare ip in 0.26)
if grep -q 'SocketAddr::new(\*dns_server, 53)' "$CR/hickory-resolver-0.25.2/src/system_conf/windows.rs" && grep -q 'NameServerConfig::udp_and_tcp(ip)' "$CR/hickory-resolver-0.26.2/src/system_conf/windows.rs"; then
pass V13 "Windows nameserver addresses read the same way in 0.25.2 and 0.26.2"
else fail V13 "Windows nameserver addresses read the same way in 0.25.2 and 0.26.2" "pattern not found"; fi
# V14 (round-2 C20) the 0.26 macOS backend accepts a scoped IPv6 nameserver as 0.25's resolv-conf path did
ckrc=$(lockck resolv-conf 0.7.6 < Cargo.lock)
if [ -z "$ckrc" ]; then skip V14 "0.26 macOS backend accepts scoped IPv6 nameservers" "resolv-conf 0.7.6 not in Cargo.lock"
elif ! getcrate resolv-conf 0.7.6 "$ckrc"; then skip V14 "0.26 macOS backend accepts scoped IPv6 nameservers" "resolv-conf fetch or checksum failed"
elif grep -q 'IpAddr::from_str(&Cow::from(&\*n))' "$CR/hickory-resolver-0.26.2/src/system_conf/apple.rs" && grep -q 'failed to parse nameserver address: {e}"))?;' "$CR/hickory-resolver-0.26.2/src/system_conf/apple.rs" && grep -q "split('%')" "$CR/resolv-conf-0.7.6/src/ip.rs"; then
fail V14 "0.26 macOS backend accepts scoped IPv6 nameservers" "apple.rs parses ServerAddresses with std IpAddr::from_str and '?', which fails the whole call on 'fe80::1%en0'; resolv-conf (0.25's macOS path) splits the scope"
else pass V14 "0.26 macOS backend accepts scoped IPv6 nameservers"; fi
fi
fi
# V12 (C13, pre-existing) an IPv6 system resolver keeps its address through client.rs's "{ip}:53" formatting
if grep -q 'format!("{}:53", ip)' src/client.rs; then
if python3 -I -c 'import ipaddress,sys; s="2001:db8::1"+":53"; sys.exit(0 if ipaddress.ip_address(s)!=ipaddress.ip_address("2001:db8::1") else 1)' 2>/dev/null; then
fail V12 "IPv6 system resolver keeps its address in client.rs" "src/client.rs formats '{}:53'; '2001:db8::1:53' is itself a valid, different IPv6 address that parse_resolver's fallback accepts"
else pass V12 "IPv6 system resolver keeps its address in client.rs"; fi
else pass V12 "IPv6 system resolver keeps its address in client.rs"; fi
# ------------------------------------------------------------------------------
summary|
The script's V12 check caught a real IPv6 bug, opened #176 for it. It's been in We won't be pasting command output or CI links here. CI runs on every PR, and its results are already on this one. |
Replaces #172, which failed to build.
Problem: hickory-resolver 0.26 removed
NameServerConfig::socket_addrin favor of anipfield, soget_system_resolvers()stopped compiling.Fix: Bump hickory-resolver to 0.26.2 and read
ns.ipinstead.Note: hickory-proto and hickory-client stay on 0.25.2, so the lockfile now carries both 0.25 and 0.26 of hickory-proto.
Tests: fmt and clippy pass. Locally,
get_system_resolvers()returns the nameserver listed in resolv.conf.