Skip to content

Instantly share code, notes, and snippets.

@koonix
Created December 21, 2024 21:02
Show Gist options
  • Save koonix/e620a7cc280673aa47740a67a6c97b9a to your computer and use it in GitHub Desktop.
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.
#!/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