diff --git a/README.md b/README.md index 8e304885e..83bb0a2f8 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,7 @@ Application Options: --refuse-any If specified, refuse ANY requests --edns Use EDNS Client Subnet extension --edns-addr= Send EDNS Client Address + --ednsopt= List of EDNS extensions to send along with the DNS query (ex: 8:deadbeaf) --ipv6-disabled If specified, all AAAA requests will be replied with NoError RCode and empty answer --bogus-nxdomain= Transform responses that contain only given IP addresses into NXDOMAIN. Can be specified multiple times. --version Prints the program version @@ -202,6 +203,25 @@ If you want to use EDNS CS feature when you're connecting to the proxy from a lo Now even if your IP address is 192.168.0.1 and it's not a public IP, the proxy will pass through 72.72.72.72 to the upstream server. + +### EDNS Generic raw option + +You can add any EDNS extension of your choice with the `--ednsopt` flag. + +The option argument for this flag is of the form: + + option_code:base64_data + +where: + +`option_code` is a 16 bit unsigned integer (0-65535) +`base64_data` is a base64 encoded byte array + +``` +DATA=$(echo -n "This is a binary string" | base64) +./dnsproxy -u 8.8.8.8:53 --ednsopt="4242:${DATA}" +``` + ### Bogus NXDomain This option is similar to dnsmasq `bogus-nxdomain`. If specified, `dnsproxy` transforms responses that contain only the given IP addresses into `NXDOMAIN`. Can be specified multiple times. @@ -210,4 +230,4 @@ In the example below, we use AdGuard DNS server that returns `0.0.0.0` for block ``` ./dnsproxy -u 176.103.130.130:53 --bogus-nxdomain=0.0.0.0 -``` \ No newline at end of file +``` diff --git a/main.go b/main.go index 4155d9fe2..7d24aa020 100644 --- a/main.go +++ b/main.go @@ -2,11 +2,14 @@ package main import ( "crypto/tls" + "encoding/base64" "fmt" "io/ioutil" "net" "os" "os/signal" + "strconv" + "strings" "syscall" "time" @@ -100,6 +103,9 @@ type Options struct { // Use Custom EDNS Client Address EDNSAddr string `long:"edns-addr" description:"Send EDNS Client Address"` + // Add user-specific EDNS exstension + EDNSOpt []string `long:"ednsopt" description:"List of EDNS extensions to send along with the DNS query (ex: 8:deadbeaf)"` + // Other settings and options // -- @@ -218,6 +224,27 @@ func createProxyConfig(options Options) proxy.Config { } } + if len(options.EDNSOpt) > 0 { + config.EDNSOpts = make(map[uint16][]byte) + for _, s := range options.EDNSOpt { + kv := strings.Split(s, ":") + if len(kv) != 2 { + log.Fatalf("parse error for '%s', --ednsopt must be of the form: option-code:base64encodeddata (ex: --ednsopt 8:SGVsbG8gd29ybGQK)", s) + } else { + r, err := strconv.ParseUint(kv[0], 10, 16) + if err != nil { + log.Fatalf("parse error for '%s', --ednsopt option-code must be a valid 16 bits numbers (between 0 and 65535)", kv[0]) + } + optionCode := uint16(r) + data, err := base64.StdEncoding.DecodeString(kv[1]) + if err != nil { + log.Fatalf("parse error for '%s', --ednsopt data must be a valid base64 encoded string: (%s)", kv[1], err) + } + config.EDNSOpts[optionCode] = data + } + } + } + if options.Fallbacks != nil { fallbacks := []upstream.Upstream{} for i, f := range options.Fallbacks { diff --git a/proxy/config.go b/proxy/config.go index f69a110f4..40a0c1521 100644 --- a/proxy/config.go +++ b/proxy/config.go @@ -72,6 +72,9 @@ type Config struct { EnableEDNSClientSubnet bool EDNSAddr net.IP // ECS IP used in request + // List of raw EDNS options + EDNSOpts map[uint16][]byte + // Cache settings // -- diff --git a/proxy/proxy.go b/proxy/proxy.go index 58ec68e74..3302174f0 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -258,6 +258,10 @@ func (p *Proxy) Resolve(d *DNSContext) error { p.processECS(d) } + if p.Config.EDNSOpts != nil { + p.processEDNSOpts(d) + } + if p.replyFromCache(d) { return nil } @@ -349,3 +353,29 @@ func (p *Proxy) processECS(d *DNSContext) { d.ecsReqIP = ip d.ecsReqMask = mask } + +// Set EDNSOpts +func (p *Proxy) processEDNSOpts(d *DNSContext) { + var o (*dns.OPT) = nil + for _, ex := range d.Req.Extra { + if ex.Header().Rrtype == dns.TypeOPT { + o = ex.(*dns.OPT) + break + } + } + + if o == nil { // OPT Record does not already exist, create it + o = new(dns.OPT) + o.SetUDPSize(4096) + o.Hdr.Name = "." + o.Hdr.Rrtype = dns.TypeOPT + d.Req.Extra = append(d.Req.Extra, o) + } + + for k, v := range p.Config.EDNSOpts { + e := new(dns.EDNS0_LOCAL) + e.Code = k + e.Data = v + o.Option = append(o.Option, e) + } +}