Skip to content

Instantly share code, notes, and snippets.

@GrahamW
Last active October 22, 2020 21:31
Show Gist options
  • Save GrahamW/7191d73f695cd95c4e535a55751d2642 to your computer and use it in GitHub Desktop.
Save GrahamW/7191d73f695cd95c4e535a55751d2642 to your computer and use it in GitHub Desktop.
Hacky DNS over HTTPS (DoH) proxy server in python
#!/usr/bin/env python3.6
# Based on pared down version of https://github.com/samuelcolvin/dnserver
# Uses cloudflare-dns.com for DoH, and their 1.1.1.1 DNS as fallback
import logging
import os
import signal
import requests
from time import sleep
from dnslib import QTYPE, DNSRecord
from dnslib.proxy import ProxyResolver
from dnslib.server import DNSServer
#from pprint import pprint
handler = logging.StreamHandler()
handler.setLevel(logging.INFO)
handler.setFormatter(logging.Formatter('%(asctime)s: %(message)s', datefmt='%H:%M:%S'))
logger = logging.getLogger(__name__)
logger.addHandler(handler)
logger.setLevel(logging.INFO)
class Resolver(ProxyResolver):
def __init__(self, upstream):
super().__init__(upstream, 53, 5)
def resolve(self, request, handler):
type_name = QTYPE[request.q.qtype]
reply = request.reply()
# pass through request to cloudflare, or we end up recursive failure
if request.q.qname == 'cloudflare-dns.com':
return super().resolve(request, handler)
#logger.info(vars(request))
thing=DNSRecord.pack(request)
h = {'content-type':'application/dns-udpwireformat', 'accept': 'application/dns-udpwireformat'}
r = requests.post('https://1.1.1.1/dns-query', data=thing, headers=h)
#logger.info(vars(r))
if r.status_code == 200:
return DNSRecord.parse(r.content)
logger.info('no local zone found, proxying %s[%s]', request.q.qname, type_name)
return super().resolve(request, handler)
def handle_sig(signum, frame):
logger.info('pid=%d, got signal: %s, stopping...', os.getpid(), signal.Signals(signum).name)
exit(0)
if __name__ == '__main__':
signal.signal(signal.SIGTERM, handle_sig)
port = int(os.getenv('PORT', 3553))
upstream = os.getenv('UPSTREAM', '1.1.1.1')
resolver = Resolver(upstream)
udp_server = DNSServer(resolver, port=port)
tcp_server = DNSServer(resolver, port=port, tcp=True)
logger.info('starting DNS server on port %d, upstream DNS server "%s"', port, upstream)
udp_server.start_thread()
tcp_server.start_thread()
try:
while udp_server.isAlive():
sleep(1)
except KeyboardInterrupt:
pass
@GrahamW
Copy link
Author

GrahamW commented Apr 9, 2018

To use with pi.hole on Raspberry pi:

  1. sudo apt install python3-pip
  2. pip3 install dnslib requests
  3. edit /etc/dnsmasq.d/01-pihole.conf, remove existing server= entries and add server=127.0.0.1#3553
  4. edit /etc/pihole/setupVars.conf and remove PIHOLE_DNS_X lines
  5. start above script, detached, nohup python3 doh_proxy.py &
  6. restart pi-hole: sudo systemctl restart dnsmasq

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment