Skip to content

Commit

Permalink
Merge branch 'master' into AG-23599-use-hostsfile-vol.2
Browse files Browse the repository at this point in the history
  • Loading branch information
EugeneOne1 committed Nov 7, 2023
2 parents eb12aec + 254e5a8 commit fa37464
Show file tree
Hide file tree
Showing 17 changed files with 388 additions and 158 deletions.
118 changes: 68 additions & 50 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,58 +49,53 @@ Usage:
dnsproxy [OPTIONS]
Application Options:
--config-path= yaml configuration file. Minimal working configuration in config.yaml.dist.
Options passed through command line will override the ones from this file.
-v, --verbose Verbose output (optional)
-o, --output= Path to the log file. If not set, write to stdout.
-l, --listen= Listening addresses
-p, --port= Listening ports. Zero value disables TCP and UDP listeners
-s, --https-port= Listening ports for DNS-over-HTTPS
-t, --tls-port= Listening ports for DNS-over-TLS
-q, --quic-port= Listening ports for DNS-over-QUIC
-y, --dnscrypt-port= Listening ports for DNSCrypt
-c, --tls-crt= Path to a file with the certificate chain
-k, --tls-key= Path to a file with the private key
--tls-min-version= Minimum TLS version, for example 1.0
--tls-max-version= Maximum TLS version, for example 1.3
--insecure Disable secure TLS certificate validation
-g, --dnscrypt-config= Path to a file with DNSCrypt configuration. You can generate one using https://github.com/ameshkov/dnscrypt
--http3 Enable HTTP/3 support
-u, --upstream= An upstream to be used (can be specified multiple times).
You can also specify path to a file with the list of servers
-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.
You can also specify path to a file with the list of servers
--private-rdns-upstream= Private DNS upstreams to use for reverse DNS lookups of private addresses, 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
--timeout= Timeout for outbound DNS queries to remote upstream servers in a
human-readable form (default: 10s)
--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.
--cache-optimistic If specified, optimistic DNS cache is enabled
-r, --ratelimit= Ratelimit (requests per second)
--refuse-any If specified, refuse ANY requests
--edns Use EDNS Client Subnet extension
--edns-addr= Send EDNS Client Address
--dns64 If specified, dnsproxy will act as a DNS64 server
--dns64-prefix= Prefix used to handle DNS64. If not specified, dnsproxy uses the 'Well-Known Prefix' 64:ff9b::.
Can be specified multiple times
--https-server-name= Set the Server header for the responses from the HTTPS server. (default: dnsproxy)
--ipv6-disabled If specified, all AAAA requests will be replied with NoError RCode and empty answer
--bogus-nxdomain= Transform the responses containing at least a single IP that matches specified addresses
and CIDRs into NXDOMAIN. Can be specified multiple times.
--udp-buf-size= Set the size of the UDP buffer in bytes. A value <= 0 will use the system default.
--max-go-routines= Set the maximum number of go routines. A value <= 0 will not not set a maximum.
--pprof If present, exposes pprof information on localhost:6060.
--version Prints the program version
--config-path= yaml configuration file. Minimal working configuration in config.yaml.dist. Options passed through command line will override the ones from this file.
-v, --verbose Verbose output (optional)
-o, --output= Path to the log file. If not set, write to stdout.
-l, --listen= Listening addresses
-p, --port= Listening ports. Zero value disables TCP and UDP listeners
-s, --https-port= Listening ports for DNS-over-HTTPS
-t, --tls-port= Listening ports for DNS-over-TLS
-q, --quic-port= Listening ports for DNS-over-QUIC
-y, --dnscrypt-port= Listening ports for DNSCrypt
-c, --tls-crt= Path to a file with the certificate chain
-k, --tls-key= Path to a file with the private key
--tls-min-version= Minimum TLS version, for example 1.0
--tls-max-version= Maximum TLS version, for example 1.3
--insecure Disable secure TLS certificate validation
-g, --dnscrypt-config= Path to a file with DNSCrypt configuration. You can generate one using https://github.com/ameshkov/dnscrypt
--http3 Enable HTTP/3 support
-u, --upstream= An upstream to be used (can be specified multiple times). You can also specify path to a file with the list of servers
-b, --bootstrap= Bootstrap DNS for DoH and DoT, can be specified multiple times (default: use system-provided)
-f, --fallback= Fallback resolvers to use when regular ones are unavailable, can be specified multiple times. You can also specify path to a file with the list of servers
--private-rdns-upstream= Private DNS upstreams to use for reverse DNS lookups of private addresses, 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
--timeout= Timeout for outbound DNS queries to remote upstream servers in a human-readable form (default: 10s)
--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.
--cache-optimistic If specified, optimistic DNS cache is enabled
-r, --ratelimit= Ratelimit (requests per second)
--ratelimit-subnet-len-ipv4= Ratelimit subnet length for IPv4. (default: 24)
--ratelimit-subnet-len-ipv6= Ratelimit subnet length for IPv6. (default: 64)
--refuse-any If specified, refuse ANY requests
--edns Use EDNS Client Subnet extension
--edns-addr= Send EDNS Client Address
--dns64 If specified, dnsproxy will act as a DNS64 server
--dns64-prefix= Prefix used to handle DNS64. If not specified, dnsproxy uses the 'Well-Known Prefix' 64:ff9b::. Can be specified multiple times
--https-server-name= Set the Server header for the responses from the HTTPS server. (default: dnsproxy)
--https-userinfo= If set, all DoH queries are required to have this basic authentication information.
--ipv6-disabled If specified, all AAAA requests will be replied with NoError RCode and empty answer
--bogus-nxdomain= Transform the responses containing at least a single IP that matches specified addresses and CIDRs into NXDOMAIN. Can be specified multiple times.
--udp-buf-size= Set the size of the UDP buffer in bytes. A value <= 0 will use the system default.
--max-go-routines= Set the maximum number of go routines. A value <= 0 will not not set a maximum.
--pprof If present, exposes pprof information on localhost:6060.
--version Prints the program version
Help Options:
-h, --help Show this help message
-h, --help Show this help message
```

## Examples
Expand Down Expand Up @@ -355,3 +350,26 @@ instead of responses containing any IP from `192.168.0.0`-`192.168.255.255`:
```
./dnsproxy -u 192.168.0.15:53 --bogus-nxdomain=192.168.0.0/16
```

### Basic Auth for DoH

By setting the `--https-userinfo` option you can use `dnsproxy` as a DoH proxy
with basic authentication requirements.

For example:

```sh
./dnsproxy\
--https-port='443'\
--https-userinfo='user:p4ssw0rd'\
--tls-crt='…/my.crt'\
--tls-key='…/my.key'\
-u '94.140.14.14:53'
```

This configuration will only allow DoH queries that contain an `Authorization`
header containing the BasicAuth credentials for user `user` with password
`p4ssw0rd`.

Add `-p 0` if you also want to disable plain-DNS handling and make `dnsproxy`
only serve DoH with Basic Auth checking.
2 changes: 2 additions & 0 deletions config.yaml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ listen-ports:
- 53
max-go-routines: 0
ratelimit: 0
ratelimit-subnet-len-ipv4: 24
ratelimit-subnet-len-ipv6: 64
udp-buf-size: 0
upstream:
- "1.1.1.1:53"
Expand Down
53 changes: 39 additions & 14 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"net/http"
"net/http/pprof"
"net/netip"
"net/url"
"os"
"os/signal"
"strings"
Expand Down Expand Up @@ -145,6 +146,14 @@ type Options struct {
// Ratelimit value
Ratelimit int `yaml:"ratelimit" short:"r" long:"ratelimit" description:"Ratelimit (requests per second)"`

// RatelimitSubnetLenIPv4 is a subnet length for IPv4 addresses used for
// rate limiting requests
RatelimitSubnetLenIPv4 int `yaml:"ratelimit-subnet-len-ipv4" long:"ratelimit-subnet-len-ipv4" description:"Ratelimit subnet length for IPv4." default:"24"`

// RatelimitSubnetLenIPv6 is a subnet length for IPv6 addresses used for
// rate limiting requests
RatelimitSubnetLenIPv6 int `yaml:"ratelimit-subnet-len-ipv6" long:"ratelimit-subnet-len-ipv6" description:"Ratelimit subnet length for IPv6." default:"64"`

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

Expand Down Expand Up @@ -174,6 +183,11 @@ type Options struct {
// Set Server header for the HTTPS server
HTTPSServerName string `yaml:"https-server-name" long:"https-server-name" description:"Set the Server header for the responses from the HTTPS server." default:"dnsproxy"`

// HTTPSUserinfo is the sole permitted userinfo for the DoH basic
// authentication. If it is set, all DoH queries are required to have this
// basic authentication information.
HTTPSUserinfo string `yaml:"https-userinfo" long:"https-userinfo" description:"If set, all DoH queries are required to have this basic authentication information."`

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

Expand Down Expand Up @@ -261,8 +275,8 @@ func run(options *Options) {
log.Info("Starting dnsproxy %s", version.Version())

// Prepare the proxy server and its configuration.
config := createProxyConfig(options)
dnsProxy := &proxy.Proxy{Config: config}
conf := createProxyConfig(options)
dnsProxy := &proxy.Proxy{Config: conf}

// Add extra handler if needed.
if options.IPv6Disabled {
Expand Down Expand Up @@ -319,9 +333,11 @@ func runPprof(options *Options) {
}

// createProxyConfig creates proxy.Config from the command line arguments
func createProxyConfig(options *Options) proxy.Config {
// Create the config
config := proxy.Config{
func createProxyConfig(options *Options) (conf proxy.Config) {
conf = proxy.Config{
RatelimitSubnetMaskIPv4: net.CIDRMask(options.RatelimitSubnetLenIPv4, netutil.IPv4BitLen),

Check failure on line 338 in main.go

View workflow job for this annotation

GitHub Actions / tests (macos-latest)

undefined: netutil.IPv4BitLen

Check failure on line 338 in main.go

View workflow job for this annotation

GitHub Actions / tests (ubuntu-latest)

undefined: netutil.IPv4BitLen
RatelimitSubnetMaskIPv6: net.CIDRMask(options.RatelimitSubnetLenIPv6, netutil.IPv6BitLen),

Check failure on line 339 in main.go

View workflow job for this annotation

GitHub Actions / tests (macos-latest)

undefined: netutil.IPv6BitLen

Check failure on line 339 in main.go

View workflow job for this annotation

GitHub Actions / tests (ubuntu-latest)

undefined: netutil.IPv6BitLen

Ratelimit: options.Ratelimit,
CacheEnabled: options.Cache,
CacheSizeBytes: options.CacheSizeBytes,
Expand All @@ -340,16 +356,25 @@ func createProxyConfig(options *Options) proxy.Config {
MaxGoroutines: options.MaxGoRoutines,
}

if uiStr := options.HTTPSUserinfo; uiStr != "" {
user, pass, ok := strings.Cut(uiStr, ":")
if ok {
conf.Userinfo = url.UserPassword(user, pass)
} else {
conf.Userinfo = url.User(user)
}
}

// TODO(e.burkov): Make these methods of [Options].
initUpstreams(&config, options)
initEDNS(&config, options)
initBogusNXDomain(&config, options)
initTLSConfig(&config, options)
initDNSCryptConfig(&config, options)
initListenAddrs(&config, options)
initDNS64(&config, options)

return config
initUpstreams(&conf, options)
initEDNS(&conf, options)
initBogusNXDomain(&conf, options)
initTLSConfig(&conf, options)
initDNSCryptConfig(&conf, options)
initListenAddrs(&conf, options)
initDNS64(&conf, options)

return conf
}

// isEmpty returns false if uc contains at least a single upstream. uc must not
Expand Down
72 changes: 67 additions & 5 deletions proxy/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import (
"fmt"
"net"
"net/netip"
"net/url"
"time"

"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil"
"github.com/ameshkov/dnscrypt/v2"
)

Expand Down Expand Up @@ -64,6 +66,14 @@ type Config struct {
// Rate-limiting and anti-DNS amplification measures
// --

// RatelimitSubnetMaskIPv4 is a subnet mask for IPv4 addresses used for
// rate limiting requests.
RatelimitSubnetMaskIPv4 net.IPMask

// RatelimitSubnetMaskIPv6 is a subnet mask for IPv6 addresses used for
// rate limiting requests.
RatelimitSubnetMaskIPv6 net.IPMask

Ratelimit int // max number of requests per second from a given IP (0 to disable)
RatelimitWhitelist []string // a list of whitelisted client IP addresses
RefuseAny bool // if true, refuse ANY requests
Expand Down Expand Up @@ -153,6 +163,11 @@ type Config struct {
// not empty.
HTTPSServerName string

// Userinfo is the sole permitted userinfo for the DoH basic authentication.
// If Userinfo is set, all DoH queries are required to have this basic
// authentication information.
Userinfo *url.Userinfo

// MaxGoroutines is the maximum number of goroutines processing DNS
// requests. Important for mobile users.
//
Expand Down Expand Up @@ -181,7 +196,8 @@ type Config struct {
PreferIPv6 bool
}

// validateConfig verifies that the supplied configuration is valid and returns an error if it's not
// validateConfig verifies that the supplied configuration is valid and returns
// an error if it's not.
func (p *Proxy) validateConfig() error {
err := p.validateListenAddrs()
if err != nil {
Expand All @@ -191,7 +207,7 @@ func (p *Proxy) validateConfig() error {

err = p.UpstreamConfig.validate()
if err != nil {
return fmt.Errorf("validating general usptreams: %w", err)
return fmt.Errorf("validating general upstreams: %w", err)
}

// Allow both [Proxy.PrivateRDNSUpstreamConfig] and [Proxy.Fallbacks] to be
Expand All @@ -207,12 +223,60 @@ func (p *Proxy) validateConfig() error {
return fmt.Errorf("validating fallbacks: %w", err)
}

err = p.validateRatelimit()
if err != nil {
return fmt.Errorf("validating ratelimit: %w", err)
}

p.logConfigInfo()

return nil
}

// validateRatelimit validates ratelimit configuration and returns an error if
// it's invalid.
func (p *Proxy) validateRatelimit() (err error) {
if p.Ratelimit == 0 {
return nil
}

if p.RatelimitSubnetMaskIPv4 == nil {
return errors.Error("ipv4 subnet mask is nil")
}

_, bits := p.RatelimitSubnetMaskIPv4.Size()
if bits != netutil.IPv4BitLen {
return fmt.Errorf("ipv4 subnet mask must contain %d bits, got %d", netutil.IPv4BitLen, bits)
}

if p.RatelimitSubnetMaskIPv6 == nil {
return errors.Error("ipv6 subnet is nil")
}

_, bits = p.RatelimitSubnetMaskIPv6.Size()
if bits != netutil.IPv6BitLen {
return fmt.Errorf("ipv6 subnet mask must contain %d bits, got %d", netutil.IPv6BitLen, bits)
}

return nil
}

// logConfigInfo logs proxy configuration information.
func (p *Proxy) logConfigInfo() {
if p.CacheMinTTL > 0 || p.CacheMaxTTL > 0 {
log.Info("Cache TTL override is enabled. Min=%d, Max=%d", p.CacheMinTTL, p.CacheMaxTTL)
}

if p.Ratelimit > 0 {
log.Info("Ratelimit is enabled and set to %d rps", p.Ratelimit)
sizeV4, _ := p.RatelimitSubnetMaskIPv4.Size()
sizeV6, _ := p.RatelimitSubnetMaskIPv6.Size()

log.Info(
"Ratelimit is enabled and set to %d rps, IPv4 subnet mask len %d, IPv6 subnet mask len %d",
p.Ratelimit,
sizeV4,
sizeV6,
)
}

if p.RefuseAny {
Expand All @@ -222,8 +286,6 @@ func (p *Proxy) validateConfig() error {
if len(p.BogusNXDomain) > 0 {
log.Info("%d bogus-nxdomain IP specified", len(p.BogusNXDomain))
}

return nil
}

// validateListenAddrs returns an error if the addresses are not configured
Expand Down
2 changes: 2 additions & 0 deletions proxy/dnscontext.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ type DNSContext struct {
QUICStream quic.Stream

// Addr is the address of the client.
//
// TODO(s.chzhen): Use [netip.AddrPort].
Addr net.Addr

// Upstream is the upstream that resolved the request. In case of cached
Expand Down
Loading

0 comments on commit fa37464

Please sign in to comment.