Skip to content

Commit

Permalink
Add some DNS fallthrough functionality.
Browse files Browse the repository at this point in the history
  • Loading branch information
wrouesnel committed Jan 10, 2018
1 parent c31238a commit a01c2b9
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 49 deletions.
9 changes: 5 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,16 @@ cache:
- tools/bin
script:
- CONCURRENT_LINTERS=1 LINTER_DEADLINE=180s make all
- tools/bin/goveralls -coverprofile=cover.out -service=travis-ci"
- tools/bin/goveralls -coverprofile=cover.out -service=travis-ci
- make release
deploy:
skip_cleanup: true
provider: releases
api_key:
secure: OGZigikTdBnXqHpTJNk/z7yPA8umuasRZ8kyHY+Fp5QArgcauS/9larHxpJ0Q1j7KSpVHbeMOsbLUod1ahIZMCmDQuVoiC89ECs+K+0+ELOKMRHgmHloEhUOH30Hr9hkgQpj/L0W/ImzyYGYAkaLzbotCJ7mTmWgigEgrkooaKLGjk9o5FV4yRvdqMPyeMQsK2KWEZ+iu1ncwbp6d6YbbUnAt+E0zWzgoPwNk6D5bMDfr5aq4Vz0PDmqrSeHjs/kSk9QjKPcnciuixJETIbD/8W6L3U5zMp9fd6qQqXCjymGe00PAfu8RDF04s42dNST/fMD1eMjHvtWkvfwPVaEgsKaBxazpflGTUOJ8rj2j3dCbKXwbhJVol3Ag7+IhAud924Nwtn7tm/Na7lN3/wfXf1kPeXos3dWr29O3vr0k5Ajm1cNK6iXUeTxq/Ikqo3do9knunSg+l7WpxNlhGR2XWGovZ6TEsW+iwJeF67+VCgR7oco505C24mCchVVDZ+/xLY7Zrm6sqkfSmduUDKGeG1EKpFjsmyLL4zpOqEzOdBfInf0VEbWeZwOZiXDnWxMAup78n82+VmaxCQVr/r33rKslrNoXI+5LEH6+2WJJ+o9mMr46Vy8VJ2GHsOneOfU51HeP4/8J7n+QLlac+z6gSpyPEkbMWgTHOswXMzn+As=
secure: SDfSox6BnnoZELv1eQxz07K32r3BevyHEQOPkmsYbANRpZvtv0nfN7Y/fADvfvvY4dRqoKJfS4oY518jbhz5zH+h+1INFAhVNMwh6TRsY2aQKkhWGZPRhqFeL6/pLIkyKFoQU/Jcfv5N1DFaXidaearudFN+VoYD/knAXPaATLmhdfUewi1gg1lNGyKtJlc1zN7ffo/o+u2NGnwo7+/+lvss0pC/KdQUI4YmrLoyDgZR5ADpZ8Kkuc5EGZOfubKl5eOxX408AfaVar4WIei61ZqkgkxOYeza1Ps0y1brBHR5DsHwWvefA76TtNzoQz4bsVPdMwV23SqPgCllH7cFlflGgle33Db6DWx+wJ274V6tcyqa1HpKwILzqsPuOD77LNQ+6eiqxUOMF2AYw8rFal/5UqeV82gMBhJAhyL8aWL5AFldJtqeZIFetxwC/eecoseaqpyrvgNPppIYQlNELKA26zKy4tDa/Pdu2GBBKDGIl3zbeijK3FH2hVLZZzFY/815QmvvsjZXcR7CIIcHxYf7nHPPWx6O+DPnPgVwGVMi03m4AXgJYIF/fOrScgmd8paFX8feMswNZRqppDzuzgKUPLT4O9TsFeAFgwrSYkUEyrcu2PkcnV0/uz1SRSwF3jKg0nNhHojXylULwwR7o/9wwZNFaOHRWHKVLtyCEE0=
skip_cleanup: true
file_glob: true
file: release/*.tar.gz
file: release/*
on:
tags: true
branch: master
repo: wrouesnel/dns-over-https-proxy
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ lint: tools
--enable-all \
--line-length=120 \
--disable=testify --disable=test --disable=goimports --disable=gofmt \
--disable=gotype $(GO_DIRS)
--disable=gotype --disable=gocyclo $(GO_DIRS)

fmt: tools
gofmt -s -w $(GO_SRC)
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ A and AAAA records (as no API to convert them to Go-DNS format is yet written,
and the Google API is still in flux).

## Usage
Just run it!
Just run it! Remember if you're behind a corporate proxy you will need to set your
`http_proxy` and `https_proxy` environment variables to contact the Google DNS servers
through them. It might also be advisable use a local caching proxy in front of this
if it's going to be a daily driver.

By default it binds to port 53, so if you have a local resolver it will fail to
start. You can test it by binding to a high port and using dig like so:
Expand Down
152 changes: 109 additions & 43 deletions cmd/dns-over-https-proxy/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,13 @@ import (
"syscall"

"encoding/json"
"fmt"
"net"
"net/http"

"strings"

"fmt"

"github.com/miekg/dns"
"github.com/wrouesnel/go.log"
)
Expand All @@ -28,6 +31,17 @@ var (
defaultServer = flag.String("default", "https://dns.google.com/resolve",
"DNS-over-HTTPS service endpoint")

prefixServer = flag.String("primary-dns", "",
"If set all DNS queries are attempted against this DNS server first before trying HTTPS")

suffixServer = flag.String("fallback-dns", "",
"If set all failed (i.e. NXDOMAIN and others) DNS queries are attempted against this DNS server after HTTPS fails.") // nolint: lll

fallthroughStatuses = flag.String("fallthrough-statuses", "NXDOMAIN",
"Comma-separated list of statuses which should cause server fallthrough")
neverDefault = flag.String("no-fallthrough", "",
"Comma-separated list of suffixes which will not be allowed to fallthrough (most useful with prefix DNS")

//routeList = flag.String("route", "",
// "List of routes where to send queries (subdomain=IP:port)")
//routes map[string]string
Expand Down Expand Up @@ -157,7 +171,20 @@ func route(w dns.ResponseWriter, req *dns.Msg) {
// return
// }
//}
proxy(*defaultServer, w, req)

fallthroughs := make(map[int]struct{})
for _, v := range strings.Split(*fallthroughStatuses, ",") {
rcode, found := dns.StringToRcode[v]
if !found {
log.Fatalln("Could not find matching Rcode integer for", v)
}

fallthroughs[rcode] = struct{}{}
}

noFallthrough := strings.Split(*neverDefault, ",")

proxy(*defaultServer, *prefixServer, *suffixServer, fallthroughs, noFallthrough, w, req)
}

//func isTransfer(req *dns.Msg) bool {
Expand All @@ -183,41 +210,17 @@ func route(w dns.ResponseWriter, req *dns.Msg) {
// return false
//}

func proxy(addr string, w dns.ResponseWriter, req *dns.Msg) {
var err error
//transport := "udp"
//if _, ok := w.RemoteAddr().(*net.TCPAddr); ok {
// transport = "tcp"
//}
//if isTransfer(req) {
// if transport != "tcp" {
// dns.HandleFailed(w, req)
// return
// }
// t := new(dns.Transfer)
// c, err := t.In(req, addr)
// if err != nil {
// dns.HandleFailed(w, req)
// return
// }
// if err = t.Out(w, req, c); err != nil {
// dns.HandleFailed(w, req)
// return
// }
// return
//}
//c := &dns.Client{Net: "tcp"}
//resp, _, err := c.Exchange(req, addr)
//if err != nil {
// dns.HandleFailed(w, req)
// return
//}
func dnsRequestProxy(addr string, transport string, req *dns.Msg) (*dns.Msg, error) {
c := &dns.Client{Net: transport}
resp, _, err := c.Exchange(req, addr)
return resp, err
}

func httpDNSRequestProxy(addr string, _ string, req *dns.Msg) (*dns.Msg, error) {
httpreq, err := http.NewRequest(http.MethodGet, addr, nil)
if err != nil {
log.Errorln("Error setting up request:", err)
dns.HandleFailed(w, req)
return
return nil, err
}

qry := httpreq.URL.Query()
Expand All @@ -233,9 +236,7 @@ func proxy(addr string, w dns.ResponseWriter, req *dns.Msg) {

httpresp, err := http.DefaultClient.Do(httpreq)
if err != nil {
log.Errorln("Error sending DNS response:", err)
dns.HandleFailed(w, req)
return
return nil, err
}
defer httpresp.Body.Close() // nolint: errcheck

Expand All @@ -244,9 +245,7 @@ func proxy(addr string, w dns.ResponseWriter, req *dns.Msg) {
decoder := json.NewDecoder(httpresp.Body)
err = decoder.Decode(&dnsResp)
if err != nil {
log.Errorln("Malformed JSON DNS response:", err)
dns.HandleFailed(w, req)
return
return nil, err
}

// Parse the google Questions to DNS RRs
Expand Down Expand Up @@ -298,9 +297,76 @@ func proxy(addr string, w dns.ResponseWriter, req *dns.Msg) {
Extra: extras,
}

// Write the response
err = w.WriteMsg(&resp)
if err != nil {
log.Errorln("Error writing DNS response:", err)
return &resp, nil
}

func isSuccess(fallthroughStatuses map[int]struct{}, resp *dns.Msg) bool {
if resp == nil {
return false
}
_, found := fallthroughStatuses[resp.Rcode]
return !found
}

func continueFallthrough(noFallthrough []string, req *dns.Msg) bool {
for _, f := range noFallthrough {
if f == "" {
continue
}
for _, q := range req.Question {
if strings.HasSuffix(q.Name, f) {
return false
}
}
}
return true
}

type proxyFunc func() (*dns.Msg, error)

func proxy(addr string, prefixServer string, suffixServer string, fallthroughStatuses map[int]struct{},
noFallthrough []string, w dns.ResponseWriter, req *dns.Msg) {

qryCanFallthrough := continueFallthrough(noFallthrough, req)

transport := "udp"
if _, ok := w.RemoteAddr().(*net.TCPAddr); ok {
transport = "tcp"
}

proxyFuncs := []proxyFunc{}

// If prefix server set, try prefix server...
if prefixServer != "" {
proxyFuncs = append(proxyFuncs, func() (*dns.Msg, error) { return dnsRequestProxy(prefixServer, transport, req) })

}

proxyFuncs = append(proxyFuncs, func() (*dns.Msg, error) { return httpDNSRequestProxy(addr, transport, req) })

// If prefix server set, try prefix server...
if suffixServer != "" {
proxyFuncs = append(proxyFuncs, func() (*dns.Msg, error) { return dnsRequestProxy(suffixServer, transport, req) })

}

for _, proxyFunc := range proxyFuncs {
resp, err := proxyFunc()
if err == nil && (isSuccess(fallthroughStatuses, resp) || !qryCanFallthrough) {
// Write the response
err = w.WriteMsg(resp)
if err != nil {
log.Errorln("Error writing DNS response:", err)
dns.HandleFailed(w, req)
}
return
}

if !qryCanFallthrough {
dns.HandleFailed(w, req)
return
}
}

dns.HandleFailed(w, req)
}

0 comments on commit a01c2b9

Please sign in to comment.