Created
June 8, 2026 08:38
-
-
Save Kinark/938682bdc29db25285576bcc22ed00bb to your computer and use it in GitHub Desktop.
A DNS-failproof curl wrapper
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # ─── 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