├── .gitignore ├── Dockerfile ├── README.md └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | dohproxy -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang AS golang 2 | COPY . ./ 3 | RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix nocgo -o /doh . 4 | # We need a regular DNS server to resolve the HTTPS api. It's a chicken egg 5 | # problem. 6 | RUN echo "8.8.8.8" > /resolv.conf 7 | 8 | FROM scratch 9 | COPY --from=golang /doh / 10 | COPY --from=golang /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ 11 | COPY --from=golang /resolv.conf /etc/resolv.conf 12 | EXPOSE 53 13 | ENTRYPOINT ["/doh"] 14 | CMD ["-host", "0.0.0.0", "-port", "53"] 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dohproxy 2 | 3 | DNS over HTTPS proxy written in golang 4 | 5 | I got interested in DNS over HTTPS after Firefox started supporting it in its latest release. I looked around to understand how it worked. Most of the implementations were too complex and did a lot of things. I read the RFC[1] and realised it was very trivial. So I tried my hand at implementing a proxy. This is just a proof of concept. 6 | 7 | To install it you can use: 8 | ``` 9 | go get github.com/satran/dohproxy 10 | ``` 11 | This assumes you have installed go. 12 | 13 | 14 | To run it use: 15 | ``` 16 | dohproxy 17 | ``` 18 | This will start the proxy on `5353` port. 19 | 20 | You can resolve addresses using: 21 | ``` 22 | dig @127.0.0.1 -p 5353 redhat.com 23 | ``` 24 | 25 | ### Running it as a docker container 26 | 27 | If you would like to run it as a docker container run: 28 | ``` 29 | docker run -it --rm -p 53:53/udp satran/dohproxy 30 | ``` 31 | This will run the proxy on localhost. You can update your `/etc/resolv.conf` file with `nameserver 127.0.0.1` to resolve all dns queries using the dohproxy. 32 | 33 | 34 | [1] https://tools.ietf.org/html/draft-ietf-doh-dns-over-https-13 35 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/base64" 5 | "flag" 6 | "fmt" 7 | "io/ioutil" 8 | "log" 9 | "net" 10 | "net/http" 11 | ) 12 | 13 | func main() { 14 | host := flag.String("host", "localhost", "interface to listen on") 15 | port := flag.Int("port", 5353, "dns port to listen on") 16 | dohserver := flag.String("dohserver", "https://mozilla.cloudflare-dns.com/dns-query", "DNS Over HTTPS server address") 17 | debug := flag.Bool("debug", false, "print debug logs") 18 | flag.Parse() 19 | 20 | if *debug { 21 | log.SetFlags(log.Lshortfile) 22 | } else { 23 | log.SetFlags(0) 24 | } 25 | 26 | if err := newUDPServer(*host, *port, *dohserver); err != nil { 27 | log.Fatalf("could not listen on %s:%d: %s", *host, *port, err) 28 | } 29 | } 30 | 31 | func newUDPServer(host string, port int, dohserver string) error { 32 | conn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.ParseIP(host), Port: port}) 33 | if err != nil { 34 | return err 35 | } 36 | for { 37 | var raw [512]byte 38 | n, addr, err := conn.ReadFromUDP(raw[:512]) 39 | if err != nil { 40 | log.Printf("could not read: %s", err) 41 | continue 42 | } 43 | log.Printf("new connection from %s:%d", addr.IP.String(), addr.Port) 44 | go proxy(dohserver, conn, addr, raw[:n]) 45 | } 46 | } 47 | 48 | func proxy(dohserver string, conn *net.UDPConn, addr *net.UDPAddr, raw []byte) { 49 | enc := base64.RawURLEncoding.EncodeToString(raw) 50 | url := fmt.Sprintf("%s?dns=%s", dohserver, enc) 51 | r, err := http.NewRequest(http.MethodGet, url, nil) 52 | if err != nil { 53 | log.Printf("could not create request: %s", err) 54 | return 55 | } 56 | r.Header.Set("Content-Type", "application/dns-message") 57 | r.Header.Set("Accept", "application/dns-message") 58 | 59 | c := http.Client{} 60 | resp, err := c.Do(r) 61 | if err != nil { 62 | log.Printf("could not perform request: %s", err) 63 | return 64 | } 65 | defer resp.Body.Close() 66 | 67 | if resp.StatusCode != http.StatusOK { 68 | log.Printf("wrong response from DOH server got %s", http.StatusText(resp.StatusCode)) 69 | return 70 | } 71 | 72 | msg, err := ioutil.ReadAll(resp.Body) 73 | if err != nil { 74 | log.Printf("could not read message from response: %s", err) 75 | return 76 | } 77 | 78 | if _, err := conn.WriteToUDP(msg, addr); err != nil { 79 | log.Printf("could not write to udp connection: %s", err) 80 | return 81 | } 82 | } 83 | --------------------------------------------------------------------------------