Skip to content

Commit

Permalink
Merge: feat: 🎸 added bogus-nxdomain option
Browse files Browse the repository at this point in the history
* commit 'baa099c4c650ec20f62fd25048a013ce0c9d7a5f':
  fix: 🐛 remove unnecessary len check
  fix: 🐛 typo
  feat: 🎸 added bogus-nxdomain option
  • Loading branch information
ameshkov committed May 8, 2020
2 parents 9df9cb0 + baa099c commit b8a91c7
Show file tree
Hide file tree
Showing 14 changed files with 1,071 additions and 813 deletions.
77 changes: 50 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,24 @@
[![GolangCI](https://golangci.com/badges/github.com/AdguardTeam/dnsproxy.svg)](https://golangci.com/r/github.com/AdguardTeam/dnsproxy)
[![Go Doc](https://godoc.org/github.com/AdguardTeam/dnsproxy?status.svg)](https://godoc.org/github.com/AdguardTeam/dnsproxy)

# DNS Proxy
# DNS Proxy <!-- omit in toc -->

A simple DNS proxy server that supports all existing DNS protocols including `DNS-over-TLS`, `DNS-over-HTTPS`, and `DNSCrypt`.

Moreover, it can work as a `DNS-over-HTTPS` and/or `DNS-over-TLS` server.

- [How to build](#how-to-build)
- [Usage](#usage)
- [Examples](#examples)
- [Simple options](#simple-options)
- [Encrypted upstreams](#encrypted-upstreams)
- [Encrypted DNS server](#encrypted-dns-server)
- [Additional features](#additional-features)
- [Fastest addr + cache-min-ttl](#fastest-addr--cache-min-ttl)
- [Specifying upstreams for domains](#specifying-upstreams-for-domains)
- [EDNS Client Subnet](#edns-client-subnet)
- [Bogus NXDomain](#bogus-nxdomain)

## How to build

You will need go v1.14 or later.
Expand All @@ -25,33 +37,34 @@ Usage:
dnsproxy [OPTIONS]
Application Options:
-v, --verbose Verbose output (optional)
-o, --output= Path to the log file. If not set, write to stdout.
-l, --listen= Listen address (default: 0.0.0.0)
-p, --port= Listen port. Zero value disables TCP and UDP listeners (default: 53)
-h, --https-port= Listen port for DNS-over-HTTPS (default: 0)
-t, --tls-port= Listen port for DNS-over-TLS (default: 0)
-c, --tls-crt= Path to a file with the certificate chain
-k, --tls-key= Path to a file with the private key
-b, --bootstrap= Bootstrap DNS for DoH and DoT, can be specified multiple times (default: 8.8.8.8:53)
-r, --ratelimit= Ratelimit (requests per second) (default: 0)
-z, --cache If specified, DNS cache is enabled
-e --cache-size= Cache size (in bytes). Default: 65536
--cache-min-ttl= Minimum TTL value for DNS entries, in seconds. Capped at 3600 seconds (1 hour).
Artificially extending TTLs should only be done with careful consideration.
--cache-max-ttl= Maximum TTL value for DNS entries, in seconds.
-a, --refuse-any If specified, refuse ANY requests
-u, --upstream= An upstream to be used (can be specified multiple times)
-f, --fallback= Fallback resolvers to use when regular ones are unavailable, can be specified multiple times
-s, --all-servers Use parallel queries to speed up resolving by querying all upstream servers simultaneously
-d, --ipv6-disabled Disable IPv6. All AAAA requests will be replied with No Error response code and empty answer
--edns Use EDNS Client Subnet extension
--edns-addr= Send EDNS Client Address
--fastest-addr Respond to A or AAAA requests only with the fastest IP address
-v, --verbose Verbose output (optional)
-o, --output= Path to the log file. If not set, write to stdout.
-l, --listen= Listen address (default: 0.0.0.0)
-p, --port= Listen port. Zero value disables TCP and UDP listeners (default: 53)
-h, --https-port= Listen port for DNS-over-HTTPS (default: 0)
-t, --tls-port= Listen port for DNS-over-TLS (default: 0)
-c, --tls-crt= Path to a file with the certificate chain
-k, --tls-key= Path to a file with the private key
-u, --upstream= An upstream to be used (can be specified multiple times)
-b, --bootstrap= Bootstrap DNS for DoH and DoT, can be specified multiple times (default: 8.8.8.8:53)
-f, --fallback= Fallback resolvers to use when regular ones are unavailable, can be specified multiple times
--all-servers If specified, parallel queries to all configured upstream servers are enabled
--fastest-addr Respond to A or AAAA requests only with the fastest IP address
--cache If specified, DNS cache is enabled
--cache-size= Cache size (in bytes). Default: 64k
--cache-min-ttl= Minimum TTL value for DNS entries, in seconds. Capped at 3600. Artificially extending TTLs should only be done with
careful consideration.
--cache-max-ttl= Maximum TTL value for DNS entries, in seconds.
-r, --ratelimit= Ratelimit (requests per second) (default: 0)
--refuse-any If specified, refuse ANY requests
--edns Use EDNS Client Subnet extension
--edns-addr= Send EDNS Client Address
--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
Help Options:
-h, --help Show this help message
--version Print DNS proxy version
-h, --help Show this help message
```

## Examples
Expand Down Expand Up @@ -154,7 +167,7 @@ If one or more domains are specified, that upstream (`upstreamString`) is used o
2. More specific domains take precedence over less specific domains, so: `--upstream=[/host.com/]1.2.3.4 --upstream=[/www.host.com/]2.3.4.5` will send queries for *.host.com to 1.2.3.4, except *.www.host.com, which will go to 2.3.4.5
3. The special server address '#' means, "use the standard servers", so: `--upstream=[/host.com/]1.2.3.4 --upstream=[/www.host.com/]#` will send queries for *.host.com to 1.2.3.4, except *.www.host.com which will be forwarded as usual.

#### Examples
**Examples**

Sends queries for `*.local` domains to `192.168.0.1:53`. Other queries are sent to `8.8.8.8:53`.
```
Expand Down Expand Up @@ -183,3 +196,13 @@ 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.

### 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.

In the example below, we use AdGuard DNS server that returns `0.0.0.0` for blocked domains, and tranform them to `NXDOMAIN`.

```
./dnsproxy -u 176.103.130.130:53 --bogus-nxdomain=0.0.0.0
```
73 changes: 55 additions & 18 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,18 @@ import (

// Options represents console arguments
type Options struct {
// Log settings
// --

// Should we write
Verbose bool `short:"v" long:"verbose" description:"Verbose output (optional)" optional:"yes" optional-value:"true"`

// Path to a log file
LogOutput string `short:"o" long:"output" description:"Path to the log file. If not set, write to stdout." default:""`

// Server settings
// --

// Server listen address
ListenAddr string `short:"l" long:"listen" description:"Listen address" default:"0.0.0.0"`

Expand All @@ -42,48 +48,66 @@ type Options struct {
// Path to the file with the private key
TLSKeyPath string `short:"k" long:"tls-key" description:"Path to a file with the private key"`

// Upstream DNS servers settings
// --

// DNS upstreams
Upstreams []string `short:"u" long:"upstream" description:"An upstream to be used (can be specified multiple times)" required:"true"`

// Bootstrap DNS
BootstrapDNS []string `short:"b" long:"bootstrap" description:"Bootstrap DNS for DoH and DoT, can be specified multiple times (default: 8.8.8.8:53)"`

// Ratelimit value
Ratelimit int `short:"r" long:"ratelimit" description:"Ratelimit (requests per second)" default:"0"`
// Fallback DNS resolver
Fallbacks []string `short:"f" long:"fallback" description:"Fallback resolvers to use when regular ones are unavailable, can be specified multiple times"`

// If true, parallel queries to all configured upstream servers
AllServers bool `long:"all-servers" description:"If specified, parallel queries to all configured upstream servers are enabled" optional:"yes" optional-value:"true"`

// Respond to A or AAAA requests only with the fastest IP address
// detected by ICMP response time or TCP connection time
FastestAddress bool `long:"fastest-addr" description:"Respond to A or AAAA requests only with the fastest IP address" optional:"yes" optional-value:"true"`

// Cache settings
// --

// If true, DNS cache is enabled
Cache bool `short:"z" long:"cache" description:"If specified, DNS cache is enabled" optional:"yes" optional-value:"true"`
Cache bool `long:"cache" description:"If specified, DNS cache is enabled" optional:"yes" optional-value:"true"`

// Cache size value
CacheSizeBytes int `short:"e" long:"cache-size" description:"Cache size (in bytes). Default: 64k"`
CacheSizeBytes int `long:"cache-size" description:"Cache size (in bytes). Default: 64k"`

// DNS cache minimum TTL value - overrides record value
CacheMinTTL uint32 `long:"cache-min-ttl" description:"Minimum TTL value for DNS entries, in seconds. Capped at 3600. Artificially extending TTLs should only be done with careful consideration."`

// DNS cache maximum TTL value - overrides record value
CacheMaxTTL uint32 `long:"cache-max-ttl" description:"Maximum TTL value for DNS entries, in seconds."`

// If true, refuse ANY requests
RefuseAny bool `short:"a" long:"refuse-any" description:"If specified, refuse ANY requests" optional:"yes" optional-value:"true"`

// DNS upstreams
Upstreams []string `short:"u" long:"upstream" description:"An upstream to be used (can be specified multiple times)" required:"true"`
// Anti-DNS amplification measures
// --

// Fallback DNS resolver
Fallbacks []string `short:"f" long:"fallback" description:"Fallback resolvers to use when regular ones are unavailable, can be specified multiple times"`
// Ratelimit value
Ratelimit int `short:"r" long:"ratelimit" description:"Ratelimit (requests per second)" default:"0"`

// If true, parallel queries to all configured upstream servers
AllServers bool `short:"s" long:"all-servers" description:"If specified, parallel queries to all configured upstream servers are enabled" optional:"yes" optional-value:"true"`
// If true, refuse ANY requests
RefuseAny bool `long:"refuse-any" description:"If specified, refuse ANY requests" optional:"yes" optional-value:"true"`

// If true, all AAAA requests will be replied with NoError RCode and empty answer
IPv6Disabled bool `short:"d" long:"ipv6-disabled" description:"If specified, all AAAA requests will be replied with NoError RCode and empty answer" optional:"yes" optional-value:"true"`
// ECS settings
// --

// Use EDNS Client Subnet extension
EnableEDNSSubnet bool `long:"edns" description:"Use EDNS Client Subnet extension" optional:"yes" optional-value:"true"`

// Use Custom EDNS Client Address
EDNSAddr string `long:"edns-addr" description:"Send EDNS Client Address"`

// Respond to A or AAAA requests only with the fastest IP address
// detected by ICMP response time or TCP connection time
FastestAddress bool `long:"fastest-addr" description:"Respond to A or AAAA requests only with the fastest IP address" optional:"yes" optional-value:"true"`
// Other settings and options
// --

// If true, all AAAA requests will be replied with NoError RCode and empty answer
IPv6Disabled bool `long:"ipv6-disabled" description:"If specified, all AAAA requests will be replied with NoError RCode and empty answer" optional:"yes" optional-value:"true"`

// Transform responses that contain only given IP addresses into NXDOMAIN
BogusNXDomain []string `long:"bogus-nxdomain" description:"Transform responses that contain only given IP addresses into NXDOMAIN. Can be specified multiple times."`

// Print DNSProxy version (just for the help)
Version bool `long:"version" description:"Prints the program version"`
Expand Down Expand Up @@ -209,6 +233,19 @@ func createProxyConfig(options Options) proxy.Config {
config.Fallbacks = fallbacks
}

if len(options.BogusNXDomain) > 0 {
bogusIP := []net.IP{}
for _, s := range options.BogusNXDomain {
ip := net.ParseIP(s)
if ip == nil {
log.Error("Invalid IP: %s", s)
} else {
bogusIP = append(bogusIP, ip)
}
}
config.BogusNXDomain = bogusIP
}

// Prepare the TLS config
if options.TLSCertPath != "" && options.TLSKeyPath != "" {
tlsConfig, err := newTLSConfig(options.TLSCertPath, options.TLSKeyPath)
Expand Down
28 changes: 28 additions & 0 deletions proxy/bogus_nxdomain.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package proxy

import (
"github.com/AdguardTeam/dnsproxy/proxyutil"
"github.com/miekg/dns"
)

// isBogusNXDomain - checks if the specified DNS message
// contains ONLY ip addresses from the Proxy.BogusNXDomain list
func (p *Proxy) isBogusNXDomain(reply *dns.Msg) bool {
if reply == nil ||
len(p.BogusNXDomain) == 0 ||
len(reply.Answer) == 0 ||
(reply.Question[0].Qtype != dns.TypeA &&
reply.Question[0].Qtype != dns.TypeAAAA) {
return false
}

for _, rr := range reply.Answer {
ip := proxyutil.GetIPFromDNSRecord(rr)
if !proxyutil.ContainsIP(p.BogusNXDomain, ip) {
return false
}
}

// All IPs are bogus if we got here
return true
}
60 changes: 60 additions & 0 deletions proxy/bogus_nxdomain_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package proxy

import (
"net"
"testing"

"github.com/AdguardTeam/dnsproxy/upstream"
"github.com/miekg/dns"
"github.com/stretchr/testify/assert"
)

func TestBogusNXDomainTypeA(t *testing.T) {
dnsProxy := createTestProxy(t, nil)
dnsProxy.CacheEnabled = true
dnsProxy.BogusNXDomain = []net.IP{net.ParseIP("4.3.2.1")}

u := testUpstream{}
dnsProxy.Upstreams = []upstream.Upstream{&u}
err := dnsProxy.Start()
assert.Nil(t, err)

// first request
// upstream answers with a bogus IP
u.aResp = new(dns.A)
u.aResp.Hdr.Rrtype = dns.TypeA
u.aResp.Hdr.Name = "host."
u.aResp.A = net.ParseIP("4.3.2.1")
u.aResp.Hdr.Ttl = 10

clientIP := net.IP{1, 2, 3, 0}
d := DNSContext{}
d.Req = createHostTestMessage("host")
d.Addr = &net.TCPAddr{
IP: clientIP,
}

err = dnsProxy.Resolve(&d)
assert.Nil(t, err)

// check response
assert.NotNil(t, d.Res)
assert.Equal(t, dns.RcodeNameError, d.Res.Rcode)

// second request
// upstream answers with a normal IP
u.aResp = new(dns.A)
u.aResp.Hdr.Rrtype = dns.TypeA
u.aResp.Hdr.Name = "host."
u.aResp.A = net.ParseIP("4.3.2.2")
u.aResp.Hdr.Ttl = 10

err = dnsProxy.Resolve(&d)
assert.Nil(t, err)

// check response
assert.NotNil(t, d.Res)
assert.Equal(t, dns.RcodeSuccess, d.Res.Rcode)

_ = dnsProxy.Stop()
}
Loading

0 comments on commit b8a91c7

Please sign in to comment.