Skip to content

Instantly share code, notes, and snippets.

@Kinark
Created June 8, 2026 08:38
Show Gist options
  • Select an option

  • Save Kinark/938682bdc29db25285576bcc22ed00bb to your computer and use it in GitHub Desktop.

Select an option

Save Kinark/938682bdc29db25285576bcc22ed00bb to your computer and use it in GitHub Desktop.
A DNS-failproof curl wrapper
# ─── failproof curl wrapper (v3) ───────────────────────────────────────────
# Cloudflare / Google / AdGuard (JSON DoH) + Quad9 (RFC 8484 wire).
# 16 workers race; first wins; rest die. POSIX sh. Self-unsets at script EXIT.
# Set CURL_WRAP_DEBUG=1 to trace to stderr.
# ───────────────────────────────────────────────────────────────────────────
curl() {
__cw_dbg() { [ -n "${CURL_WRAP_DEBUG:-}" ] && printf 'cw: %s\n' "$*" >&2; }
# ── 1. argv scan ────────────────────────────────────────────────────────
__cw_hosts='' ; __cw_skip=0
for __cw_a in "$@"; do
if [ "$__cw_skip" -eq 1 ]; then __cw_skip=0; continue; fi
case "$__cw_a" in
-o|-O|-X|-H|-d|-D|-F|-u|-A|-e|-b|-c|-K|-T|-Y|-y|-w|-E|-z|-r|\
--url|--data|--data-raw|--data-binary|--data-urlencode|--header|\
--output|--upload-file|--user|--user-agent|--referer|--cookie|\
--cookie-jar|--config|--cert|--key|--cacert|--capath|--resolve|\
--connect-to|--proxy|--proxy-user|--form|--form-string|\
--request|--write-out|--max-time|--connect-timeout|--retry|\
--range|--time-cond) __cw_skip=1; continue ;;
--url=*) __cw_a=${__cw_a#--url=} ;;
-*) continue ;;
esac
case "$__cw_a" in
*://*|*.*) __cw_hosts="$__cw_hosts $__cw_a" ;;
esac
done
[ -z "$__cw_hosts" ] && { __cw_dbg "no URL found, pass-through"; command curl "$@"; return $?; }
__cw_dbg "URLs: $__cw_hosts"
# ── 2. workspace ────────────────────────────────────────────────────────
__cw_tmp=$(mktemp -d 2>/dev/null) || \
__cw_tmp=${TMPDIR:-/tmp}/cw.$$.$(awk 'BEGIN{srand();print int(rand()*1e9)}')
mkdir -p "$__cw_tmp" 2>/dev/null || { command curl "$@"; return $?; }
# ── 3. race per host ────────────────────────────────────────────────────
__cw_resolves=''
for __cw_url in $__cw_hosts; do
__cw_host=$(__cw_host_of "$__cw_url")
__cw_port=$(__cw_port_of "$__cw_url")
[ -z "$__cw_host" ] && continue
case "$__cw_host" in
*:*|[0-9]*.[0-9]*.[0-9]*.[0-9]*)
__cw_dbg "$__cw_host is literal, skipping resolution"; continue ;;
esac
__cw_dbg "racing for $__cw_host"
__cw_ans=$(__cw_race "$__cw_host" "$__cw_tmp") || {
__cw_dbg "no resolver answered for $__cw_host"; continue
}
__cw_dbg "winner IPs: $(echo $__cw_ans)"
for __cw_ip in $__cw_ans; do
for __cw_p in 80 443 ${__cw_port:-}; do
[ -n "$__cw_p" ] || continue
__cw_resolves="$__cw_resolves --resolve $__cw_host:$__cw_p:$__cw_ip"
done
done
done
rm -rf "$__cw_tmp" 2>/dev/null
__cw_dbg "final: command curl$__cw_resolves $*"
# shellcheck disable=SC2086
command curl $__cw_resolves "$@"
}
# ── helpers ────────────────────────────────────────────────────────────────
__cw_host_of() {
__h=$1; __h=${__h#*://}; __h=${__h%%[/?#]*}; __h=${__h#*@}
case "$__h" in
\[*\]*) __h=${__h#\[}; __h=${__h%%\]*} ;;
*:*) case "${__h##*:}" in
''|*[!0-9]*) ;;
*) __h=${__h%:*} ;;
esac ;;
esac
printf '%s\n' "$__h"
}
__cw_port_of() {
__h=$1; __h=${__h#*://}; __h=${__h%%[/?#]*}; __h=${__h#*@}
case "$__h" in
\[*\]:*) printf '%s\n' "${__h##*]:}" ;;
*:*) case "${__h##*:}" in
''|*[!0-9]*) ;;
*) printf '%s\n' "${__h##*:}" ;;
esac ;;
esac
}
__cw_race() {
__host=$1
__safe=$(printf '%s' "$__host" | tr -c 'A-Za-z0-9._-' '_')
__dir=$2/race.$__safe.$$
mkdir -p "$__dir" || return 1
: > "$__dir/pids"
cat > "$__dir/list" <<'__CW_EOF__'
cloudflare cloudflare-dns.com 1.1.1.1 /dns-query json
cloudflare cloudflare-dns.com 1.0.0.1 /dns-query json
cloudflare cloudflare-dns.com 2606:4700:4700::1111 /dns-query json
cloudflare cloudflare-dns.com 2606:4700:4700::1001 /dns-query json
google dns.google 8.8.8.8 /resolve json
google dns.google 8.8.4.4 /resolve json
google dns.google 2001:4860:4860::8888 /resolve json
google dns.google 2001:4860:4860::8844 /resolve json
adguard dns.adguard-dns.com 94.140.14.14 /resolve json
adguard dns.adguard-dns.com 94.140.15.15 /resolve json
adguard dns.adguard-dns.com 2a10:50c0::ad1:ff /resolve json
adguard dns.adguard-dns.com 2a10:50c0::ad2:ff /resolve json
quad9 dns.quad9.net 9.9.9.9 /dns-query wire
quad9 dns.quad9.net 149.112.112.112 /dns-query wire
quad9 dns.quad9.net 2620:fe::fe /dns-query wire
quad9 dns.quad9.net 2620:fe::9 /dns-query wire
__CW_EOF__
__slot=0
while IFS=' ' read -r __prov __rhost __rip __path __mode; do
[ -z "$__prov" ] && continue
__slot=$((__slot + 1))
__cw_worker "$__host" "$__rhost" "$__rip" "$__path" "$__mode" \
"$__dir" "$__slot" "$__prov" &
echo $! >> "$__dir/pids"
done < "$__dir/list"
# Poll for winner, ~3 s ceiling regardless of sleep precision.
if sleep 0.05 2>/dev/null; then __step=0.05; __max=60
else __step=1; __max=3
fi
__i=0
while [ ! -s "$__dir/winner" ] && [ "$__i" -lt "$__max" ]; do
sleep "$__step"
__i=$((__i + 1))
done
# Reap stragglers.
while read -r __p; do kill -TERM "$__p" 2>/dev/null; done < "$__dir/pids"
sleep "$__step" 2>/dev/null
while read -r __p; do kill -KILL "$__p" 2>/dev/null; done < "$__dir/pids"
wait 2>/dev/null
[ -s "$__dir/winner" ] || return 1
cat "$__dir/winner"
}
__cw_worker() {
__q=$1; __rh=$2; __rip=$3; __path=$4; __mode=$5; __dir=$6; __slot=$7; __prov=$8
__out=$__dir/r.$__slot
if [ "$__mode" = json ]; then
__cw_doh_json "$__q" "$__rh" "$__rip" "$__path" > "$__out" 2>/dev/null
else
__cw_doh_wire "$__q" "$__rh" "$__rip" "$__path" "$__dir/q.$__slot" \
> "$__out" 2>/dev/null
fi
if [ -s "$__out" ]; then
if mkdir "$__dir/lock" 2>/dev/null; then
mv "$__out" "$__dir/winner" 2>/dev/null
[ -n "${CURL_WRAP_DEBUG:-}" ] && \
printf 'cw: winner=%s via %s\n' "$__prov" "$__rip" >&2
fi
fi
}
# JSON DoH — A then AAAA. Bracket v6 only on the IP literal in URL, not --resolve.
__cw_doh_json() {
__q=$1; __rh=$2; __rip=$3; __path=$4
for __t in A AAAA; do
command curl -sS --max-time 2 --connect-timeout 2 \
--resolve "$__rh:443:$__rip" \
-H 'Accept: application/dns-json' \
"https://$__rh$__path?name=$__q&type=$__t" \
| LC_ALL=C awk -v t="$__t" '
{ gsub(/[\r\n\t]/," ");
while (match($0, /"data"[ ]*:[ ]*"[^"]+"/)) {
s = substr($0, RSTART, RLENGTH);
sub(/^"data"[ ]*:[ ]*"/, "", s); sub(/"$/, "", s);
if (t=="A" && s ~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/) print s;
if (t=="AAAA" && s ~ /^[0-9A-Fa-f:]+$/ && s ~ /:/) print s;
$0 = substr($0, RSTART+RLENGTH);
} }'
done
}
# Wire DoH (Quad9). Query built with shell printf → NULs survive in the file.
__cw_doh_wire() {
__q=$1; __rh=$2; __rip=$3; __path=$4; __qfile=$5
for __t in 1 28; do
__cw_build_query "$__q" "$__t" "$__qfile" || continue
[ -s "$__qfile" ] || continue
command curl -sS --max-time 2 --connect-timeout 2 \
--resolve "$__rh:443:$__rip" \
-H 'Content-Type: application/dns-message' \
-H 'Accept: application/dns-message' \
--data-binary "@$__qfile" \
-X POST "https://$__rh$__path" \
| __cw_parse_wire "$__t"
done
}
# Pure-shell DNS query builder. NULs preserved by writing straight to a file.
__cw_build_query() {
__name=$1; __type=$2; __out=$3
: > "$__out" || return 1
# Header: ID=0 FLAGS=RD QD=1 AN=0 NS=0 AR=0
printf '\000\000\001\000\000\001\000\000\000\000\000\000' >> "$__out"
# QNAME labels
__save_ifs=$IFS
IFS=.
# shellcheck disable=SC2086
set -- $__name
IFS=$__save_ifs
for __lbl in "$@"; do
__len=${#__lbl}
[ "$__len" -eq 0 ] || [ "$__len" -gt 63 ] && return 1
printf '\\%03o' "$__len" | (
read -r __esc; printf '%b%s' "$__esc" "$__lbl"
) >> "$__out"
done
printf '\000' >> "$__out"
# QTYPE big-endian
__hi=$((__type / 256)); __lo=$((__type % 256))
printf '\\%03o\\%03o' "$__hi" "$__lo" | (
read -r __esc; printf '%b' "$__esc"
) >> "$__out"
# QCLASS = IN
printf '\000\001' >> "$__out"
return 0
}
# Parse DNS wire response — portable hex→int (no strtonum).
__cw_parse_wire() {
__type=$1
od -An -v -tx1 2>/dev/null | tr -d ' \n' | LC_ALL=C awk -v t="$__type" '
function hx(c, v) { v = index("0123456789abcdef", tolower(c)); return v - 1 }
function byte(s, i) { return hx(substr(s,i,1))*16 + hx(substr(s,i+1,1)) }
{
h = $0;
if (t==1) { pat="00010001[0-9a-f]{8}0004"; len=8 }
else { pat="001c0001[0-9a-f]{8}0010"; len=32 }
while (match(h, pat)) {
start = RSTART + RLENGTH;
d = substr(h, start, len);
if (length(d) < len) break;
if (t==1) {
printf "%d.%d.%d.%d\n",
byte(d,1), byte(d,3), byte(d,5), byte(d,7);
} else {
printf "%s:%s:%s:%s:%s:%s:%s:%s\n",
substr(d,1,4), substr(d,5,4),
substr(d,9,4), substr(d,13,4),
substr(d,17,4), substr(d,21,4),
substr(d,25,4), substr(d,29,4);
}
h = substr(h, start + len);
}
}'
}
# ── self-erasing EXIT trap, chained via eval-indirection (no quote hell) ───
__cw_prev_trap=$(trap | sed -n "/ EXIT\$/{s/^trap -- '//;s/' EXIT\$//;p;q;}")
# shellcheck disable=SC2064
trap '
[ -n "$__cw_prev_trap" ] && eval "$__cw_prev_trap"
unset -f curl __cw_host_of __cw_port_of __cw_race __cw_worker \
__cw_doh_json __cw_doh_wire __cw_build_query __cw_parse_wire \
2>/dev/null
unset __cw_prev_trap __cw_tmp __cw_hosts __cw_resolves __cw_url \
__cw_host __cw_port __cw_ans __cw_ip __cw_p __cw_a __cw_skip \
2>/dev/null
' EXIT
# ───────────────────────────────────────────────────────────────────────────
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment