Created
December 21, 2024 21:02
-
-
Save koonix/e620a7cc280673aa47740a67a6c97b9a to your computer and use it in GitHub Desktop.
Script that returns a list of DNS-over-HTTPS servers that are accessible directly via IP address.
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
#!/bin/bash | |
set -eu -o pipefail | |
# Script that returns a list of DNS-over-HTTPS servers that are accessible directly via IP address. | |
# https://dnscrypt.info/public-servers | |
# https://dnscrypt.info/stamps-specifications | |
main() | |
{ | |
VERBOSE=false | |
case ${1:-} in | |
-v|--verbose) VERBOSE=true ;; | |
esac | |
log 'fetching list of dns servers' | |
curl -fsSL 'https://raw.githubusercontent.com/DNSCrypt/dnscrypt-resolvers/master/v3/public-resolvers.md' | | |
grep 'sdns://\S\+' | | |
parse_pipe | | |
sort -u | | |
check | |
} | |
parse() | |
{ | |
local stamp=$1 props addr_len addr | |
stamp=$(base64pad "${stamp#sdns://}") | |
# get type | |
type=$(<<< "$stamp" basenc --decode --base64url | xxd -p -c0 -l1) | |
case $type in | |
00) type=plain ;; | |
01) type=dnscrypt ;; | |
02) type=dns-over-https ;; | |
03) type=dns-over-tls ;; | |
04) type=dns-over-quic ;; | |
05) type=oblivious-doh-target ;; | |
*) | |
echo "unknown type: 0x$type" >&2 | |
return | |
;; | |
esac | |
# filter based on type | |
case $type in | |
dns-over-https) ;; | |
*) return ;; | |
esac | |
# get properties | |
props=$(<<< "$stamp" basenc --decode --base64url | xxd -p -c1 -s1 -l8 | tac | tr -d '\n') | |
has_dnssec=$(( 0 != ( 0x$props & ( 1 << 0 ) ) )) | |
has_logging=$(( 0 == ( 0x$props & ( 1 << 1 ) ) )) | |
has_censoring=$(( 0 == ( 0x$props & ( 1 << 2 ) ) )) | |
# filter based on props | |
case $has_censoring in | |
0) ;; | |
*) return ;; | |
esac | |
# get address length | |
addr_len=$(<<< "$stamp" basenc --decode --base64url | xxd -p -c0 -s9 -l1) | |
addr_len=$(( 0x$addr_len )) | |
# skip servers with no IP address | |
case $addr_len in | |
0) return ;; | |
esac | |
# get address | |
addr=$(<<< "$stamp" basenc --decode --base64url | xxd -p -c0 -s10 -l"$addr_len") | |
addr=$(<<< "$addr" xxd -r -p) | |
# skip IPv6 addresses | |
case $addr in | |
'['*']'*) return ;; | |
esac | |
echo "https://$addr/dns-query" | |
} | |
parse_pipe() | |
{ | |
while IFS= read -r stamp; do | |
parse "$stamp" | |
done | |
} | |
check() | |
{ | |
dir=$(mktemp -d) | |
trap 'rm -rf -- "$dir"' EXIT | |
while IFS= read -r dns; do | |
while [[ $(jobs -p | wc -l) -ge 10 ]]; do | |
rsleep 0.1 | |
done | |
log "checking $dns" | |
( | |
if timeout 15 curl -f --doh-url "$dns" https://gstatic.com/generate_204 >/dev/null 2>&1; then | |
file=$(mktemp "$dir/XXXXXXXXXXXXXXXXXXXX") | |
echo "$dns" > "$file" | |
fi | |
) & | |
done | |
wait | |
log done. | |
set -- "$dir"/* | |
if [[ -e $1 ]]; then | |
cat -- "$dir"/* | |
fi | |
} | |
base64pad() | |
{ | |
case $(( ${#1} % 4 )) in | |
2) printf '%s\n' "$1==" ;; | |
3) printf '%s\n' "$1=" ;; | |
*) printf '%s\n' "$1" ;; | |
esac | |
} | |
log() | |
{ | |
if [[ $VERBOSE == true ]]; then | |
printf '%s\n' "$@" >&2 | |
fi | |
} | |
exec {sleepfd}<> <(:) | |
rsleep() { read -t "$1" -u "$sleepfd" ||: ;} | |
main "$@" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment