Last active
November 16, 2022 20:02
-
-
Save mschmitt/6c8e4b9978a166a06a54b413f194cd68 to your computer and use it in GitHub Desktop.
doh.sh - A highly non-scalable CGI DNS-over-HTTPS proxy in Bash. Have fun.
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 | |
# A highly non-scalable CGI DNS-over-HTTPS proxy in Bash. Have fun. | |
# Here's an endpoint running this script: https://doh.team-frickel.de | |
# Relevant Firefox settings: | |
# network.trr.mode = 2 -> DoH and fall back to DNS (default) | |
# network.trr.mode = 3 -> DoH only -> MUST use bootstrapAddress | |
# network.trr.uri = https://doh.team-frickel.de | |
# network.trr.useGET = true -> Semi-useful for debugging and testing (default false) | |
# network.trr.excluded-domains = localnet.name -> Your intranet domain | |
# network.trr.max-fails -> Raise to 11 when talking to this wonky script | |
# network.trr.bootstrapAddress = 217.182.197.102 -> No longer required since Firefox 73 or so. | |
# External requirements: base64, socat, wc | |
# All data received and sent needs to be stored in tmpfiles | |
# because Bash does not support raw binary data in variables. | |
function cleanup(){ | |
rm -f "$tmp_query" | |
rm -f "$tmp_response" | |
} | |
trap cleanup INT QUIT TERM EXIT | |
tmp_query=$(mktemp) | |
tmp_response=$(mktemp) | |
if [[ "${REQUEST_METHOD}" == 'GET' ]] | |
then | |
# GET request: base64url-encoded raw DNS query data | |
# Remove leading dns= | |
query_string1="${QUERY_STRING//dns=/}" | |
# URL-decode: substitute [-+] with [_/] | |
query_string2="${query_string1//-/+}" | |
query_string3="${query_string2//_/\/}" | |
# URL-decode: substitute %3d with = | |
query_string4="${query_string3//%3d/=}" | |
cooked_query_string=${query_string4} | |
# base64url may omit the %3d or = padding so retry decoding twice with added = | |
for attempt in 1 2 3 | |
do | |
if echo "${cooked_query_string}" | base64 -d > "$tmp_query" 2>/dev/null | |
then | |
break | |
else | |
if [[ ${attempt} -eq 3 ]] | |
then | |
printf "Failed to decode after padding twice: %s\n" \ | |
"${cooked_query_string}" >&2 | |
exit 1 # 500 Internal Server Error | |
fi | |
cooked_query_string+='=' | |
fi | |
done | |
# echo "Serving GET request" >&2 | |
elif [[ "${REQUEST_METHOD}" == 'POST' ]] | |
then | |
# POST request: raw DNS query data in body | |
cat > "$tmp_query" | |
# echo "Serving POST request" >&2 | |
else | |
echo "Skipping invalid request method" >&2 | |
exit 1 # 500 Internal Server Error | |
fi | |
# No query received. | |
if [[ ! -s "$tmp_query" ]] | |
then | |
printf "Content-Type: text/plain\n\n" | |
printf "This is a DNS-over-HTTPS resolver.\n" | |
echo "Responding to missing DNS query." >&2 | |
exit 0 # 200 OK | |
fi | |
# Pass unmodified query to upstream server and return unmodified response. | |
# (Tmpfile required to determine content-length.) | |
# Time out the UDP connection early to speed up timely responses. | |
# Repeat for a while and raise timeout until we do have a response. | |
for timeout in 0.05 0.1 0.2 0.4 0.8 1.5 3.0 5.0 10.0 15.0 | |
do | |
socat -t "${timeout}" - UDP-SENDTO:[::1]:53 < "$tmp_query" > "$tmp_response" | |
if [[ -s "$tmp_response" ]] | |
then | |
break | |
fi | |
done | |
# Exit with error if no response so far | |
if [[ ! -s "$tmp_response" ]] | |
then | |
echo "No response from name server received." >&2 | |
exit 1 # 500 Internal Server Error | |
fi | |
printf "Content-Type: application/dns-message\n" | |
printf "X-Upstream-Timing: Max timeout %s s\n" "${timeout}" | |
printf "Content-Length: %s\n\n" "$(wc -c < "$tmp_response")" | |
cat "$tmp_response" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment