diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e21e8d8..60167c75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,11 +15,13 @@ and this project adheres to ### Added +- DNSCrypt protocol support [#1361]. - A 5 second wait period until a DHCP server's network interface gets an IP address ([#2304]). - `$dnstype` modifier for filters ([#2337]). - HTTP API request body size limit ([#2305]). +[#1361]: https://github.com/AdguardTeam/AdGuardHome/issues/1361 [#2304]: https://github.com/AdguardTeam/AdGuardHome/issues/2304 [#2305]: https://github.com/AdguardTeam/AdGuardHome/issues/2305 [#2337]: https://github.com/AdguardTeam/AdGuardHome/issues/2337 diff --git a/go.mod b/go.mod index 03dd3446..8b232972 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/AdguardTeam/golibs v0.4.4 github.com/AdguardTeam/urlfilter v0.13.0 github.com/NYTimes/gziphandler v1.1.1 + github.com/ameshkov/dnscrypt/v2 v2.0.0 github.com/beefsack/go-rate v0.0.0-20200827232406-6cde80facd47 // indirect github.com/fsnotify/fsnotify v1.4.9 github.com/go-ping/ping v0.0.0-20201115131931-3300c582a663 diff --git a/go.sum b/go.sum index 7a7c1876..af30b9bd 100644 --- a/go.sum +++ b/go.sum @@ -28,8 +28,6 @@ github.com/AdguardTeam/golibs v0.4.3/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKU github.com/AdguardTeam/golibs v0.4.4 h1:cM9UySQiYFW79zo5XRwnaIWVzfW4eNXmZktMrWbthpw= github.com/AdguardTeam/golibs v0.4.4/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4= github.com/AdguardTeam/gomitmproxy v0.2.0/go.mod h1:Qdv0Mktnzer5zpdpi5rAwixNJzW2FN91LjKJCkVbYGU= -github.com/AdguardTeam/urlfilter v0.12.3 h1:FMjQG0eTgrr8xA3z2zaLVcCgGdpzoECPGWwgPjtwPNs= -github.com/AdguardTeam/urlfilter v0.12.3/go.mod h1:1fcCQx5TGJANrQN6sHNNM9KPBl7qx7BJml45ko6vru0= github.com/AdguardTeam/urlfilter v0.13.0 h1:MfO46K81JVTkhgP6gRu/buKl5wAOSfusjiDwjT1JN1c= github.com/AdguardTeam/urlfilter v0.13.0/go.mod h1:klx4JbOfc4EaNb5lWLqOwfg+pVcyRukmoJRvO55lL5U= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= @@ -109,8 +107,6 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI= github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= -github.com/go-ping/ping v0.0.0-20201022122018-3977ed72668a h1:O9xspHB2yrvKfMQ1m6OQhqe37i5yvg0dXAYMuAjugmM= -github.com/go-ping/ping v0.0.0-20201022122018-3977ed72668a/go.mod h1:35JbSyV/BYqHwwRA6Zr1uVDm1637YlNOU61wI797NPI= github.com/go-ping/ping v0.0.0-20201115131931-3300c582a663 h1:jI2GiiRh+pPbey52EVmbU6kuLiXqwy4CXZ4gwUBj8Y0= github.com/go-ping/ping v0.0.0-20201115131931-3300c582a663/go.mod h1:35JbSyV/BYqHwwRA6Zr1uVDm1637YlNOU61wI797NPI= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -236,8 +232,6 @@ github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCV github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/kardianos/service v1.1.0 h1:QV2SiEeWK42P0aEmGcsAgjApw/lRxkwopvT+Gu6t1/0= -github.com/kardianos/service v1.1.0/go.mod h1:RrJI2xn5vve/r32U5suTbeaSGoMU6GbNPoj36CVYcHc= github.com/kardianos/service v1.2.0 h1:bGuZ/epo3vrt8IPC7mnKQolqFeYJb7Cs8Rk4PSOBB/g= github.com/kardianos/service v1.2.0/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM= github.com/karrick/godirwalk v1.10.12 h1:BqUm+LuJcXjGv1d2mj3gBiQyrQ57a0rYoAmhvJQ7RDU= @@ -259,8 +253,6 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lucas-clemente/quic-go v0.18.1 h1:DMR7guC0NtVS8zNZR3IO7NARZvZygkSC56GGtC6cyys= github.com/lucas-clemente/quic-go v0.18.1/go.mod h1:yXttHsSNxQi8AWijC/vLP+OJczXqzHSOcJrM5ITUlCg= -github.com/lucas-clemente/quic-go v0.19.0 h1:IG5lB7DfHl6eZ7WTBVL8bnbDg0JGwDv906l6JffQbyg= -github.com/lucas-clemente/quic-go v0.19.0/go.mod h1:ZUygOqIoai0ASXXLJ92LTnKdbqh9MHCLTX6Nr1jUrK0= github.com/lucas-clemente/quic-go v0.19.1 h1:J9TkQJGJVOR3UmGhd4zdVYwKSA0EoXbLRf15uQJ6gT4= github.com/lucas-clemente/quic-go v0.19.1/go.mod h1:ZUygOqIoai0ASXXLJ92LTnKdbqh9MHCLTX6Nr1jUrK0= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= @@ -467,8 +459,6 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnk golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9 h1:umElSU9WZirRdgu2yFHY0ayQkEnKiOC1TtM3fWXFnoU= -golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9 h1:phUcVbl53swtrUN8kQEXFhUxPlIlWyBfKmidCu7P95o= golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -554,7 +544,6 @@ golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/internal/dnsforward/config.go b/internal/dnsforward/config.go index 73f5cb6d..881174d1 100644 --- a/internal/dnsforward/config.go +++ b/internal/dnsforward/config.go @@ -15,6 +15,7 @@ import ( "github.com/AdguardTeam/dnsproxy/proxy" "github.com/AdguardTeam/dnsproxy/upstream" "github.com/AdguardTeam/golibs/log" + "github.com/ameshkov/dnscrypt/v2" ) // FilteringConfig represents the DNS filtering configuration of AdGuard Home @@ -114,6 +115,15 @@ type TLSConfig struct { dnsNames []string } +// DNSCryptConfig is the DNSCrypt server configuration struct. +type DNSCryptConfig struct { + UDPListenAddr *net.UDPAddr + TCPListenAddr *net.TCPAddr + ProviderName string + ResolverCert *dnscrypt.Cert + Enabled bool +} + // ServerConfig represents server configuration. // The zero ServerConfig is empty and ready for use. type ServerConfig struct { @@ -124,6 +134,7 @@ type ServerConfig struct { FilteringConfig TLSConfig + DNSCryptConfig TLSAllowUnencryptedDOH bool TLSv12Roots *x509.CertPool // list of root CAs for TLSv1.2 @@ -189,6 +200,13 @@ func (s *Server) createProxyConfig() (proxy.Config, error) { return proxyConfig, err } + if s.conf.DNSCryptConfig.Enabled { + proxyConfig.DNSCryptUDPListenAddr = []*net.UDPAddr{s.conf.DNSCryptConfig.UDPListenAddr} + proxyConfig.DNSCryptTCPListenAddr = []*net.TCPAddr{s.conf.DNSCryptConfig.TCPListenAddr} + proxyConfig.DNSCryptProviderName = s.conf.DNSCryptConfig.ProviderName + proxyConfig.DNSCryptResolverCert = s.conf.DNSCryptConfig.ResolverCert + } + // Validate proxy config if proxyConfig.UpstreamConfig == nil || len(proxyConfig.UpstreamConfig.Upstreams) == 0 { return proxyConfig, errors.New("no default upstream servers configured") diff --git a/internal/home/config.go b/internal/home/config.go index 2f1e7da7..ed81c56e 100644 --- a/internal/home/config.go +++ b/internal/home/config.go @@ -99,6 +99,16 @@ type tlsConfigSettings struct { PortDNSOverTLS int `yaml:"port_dns_over_tls" json:"port_dns_over_tls,omitempty"` // DNS-over-TLS port. If 0, DOT will be disabled PortDNSOverQUIC uint16 `yaml:"port_dns_over_quic" json:"port_dns_over_quic,omitempty"` // DNS-over-QUIC port. If 0, DoQ will be disabled + // PortDNSCrypt is the port for DNSCrypt requests. If it's zero, + // DNSCrypt is disabled. + PortDNSCrypt int `yaml:"port_dnscrypt" json:"port_dnscrypt"` + // DNSCryptConfigFile is the path to the DNSCrypt config file. Must be + // set if PortDNSCrypt is not zero. + // + // See https://github.com/AdguardTeam/dnsproxy and + // https://github.com/ameshkov/dnscrypt. + DNSCryptConfigFile string `yaml:"dnscrypt_config_file" json:"dnscrypt_config_file"` + // Allow DOH queries via unencrypted HTTP (e.g. for reverse proxying) AllowUnencryptedDOH bool `yaml:"allow_unencrypted_doh" json:"allow_unencrypted_doh"` diff --git a/internal/home/dns.go b/internal/home/dns.go index 9c420e94..1090d9be 100644 --- a/internal/home/dns.go +++ b/internal/home/dns.go @@ -3,8 +3,10 @@ package home import ( "fmt" "net" + "os" "path/filepath" + "github.com/AdguardTeam/AdGuardHome/internal/agherr" "github.com/AdguardTeam/AdGuardHome/internal/dnsfilter" "github.com/AdguardTeam/AdGuardHome/internal/dnsforward" "github.com/AdguardTeam/AdGuardHome/internal/querylog" @@ -12,6 +14,8 @@ import ( "github.com/AdguardTeam/AdGuardHome/internal/util" "github.com/AdguardTeam/dnsproxy/proxy" "github.com/AdguardTeam/golibs/log" + "github.com/ameshkov/dnscrypt/v2" + yaml "gopkg.in/yaml.v2" ) // Called by other modules when configuration is changed @@ -70,7 +74,12 @@ func initDNSServer() error { } Context.dnsServer = dnsforward.NewServer(p) Context.clients.dnsServer = Context.dnsServer - dnsConfig := generateServerConfig() + dnsConfig, err := generateServerConfig() + if err != nil { + closeDNSServer() + return fmt.Errorf("generateServerConfig: %w", err) + } + err = Context.dnsServer.Prepare(&dnsConfig) if err != nil { closeDNSServer() @@ -104,10 +113,11 @@ func onDNSRequest(d *proxy.DNSContext) { } } -func generateServerConfig() dnsforward.ServerConfig { - newconfig := dnsforward.ServerConfig{ - UDPListenAddr: &net.UDPAddr{IP: net.ParseIP(config.DNS.BindHost), Port: config.DNS.Port}, - TCPListenAddr: &net.TCPAddr{IP: net.ParseIP(config.DNS.BindHost), Port: config.DNS.Port}, +func generateServerConfig() (newconfig dnsforward.ServerConfig, err error) { + bindHost := net.ParseIP(config.DNS.BindHost) + newconfig = dnsforward.ServerConfig{ + UDPListenAddr: &net.UDPAddr{IP: bindHost, Port: config.DNS.Port}, + TCPListenAddr: &net.TCPAddr{IP: bindHost, Port: config.DNS.Port}, FilteringConfig: config.DNS.FilteringConfig, ConfigModified: onConfigModified, HTTPRegister: httpRegister, @@ -121,25 +131,76 @@ func generateServerConfig() dnsforward.ServerConfig { if tlsConf.PortDNSOverTLS != 0 { newconfig.TLSListenAddr = &net.TCPAddr{ - IP: net.ParseIP(config.DNS.BindHost), + IP: bindHost, Port: tlsConf.PortDNSOverTLS, } } if tlsConf.PortDNSOverQUIC != 0 { newconfig.QUICListenAddr = &net.UDPAddr{ - IP: net.ParseIP(config.DNS.BindHost), + IP: bindHost, Port: int(tlsConf.PortDNSOverQUIC), } } + + if tlsConf.PortDNSCrypt != 0 { + newconfig.DNSCryptConfig, err = newDNSCrypt(bindHost, tlsConf) + if err != nil { + // Don't wrap the error, because it's already + // wrapped by newDNSCrypt. + return dnsforward.ServerConfig{}, err + } + } } + newconfig.TLSv12Roots = Context.tlsRoots newconfig.TLSCiphers = Context.tlsCiphers newconfig.TLSAllowUnencryptedDOH = tlsConf.AllowUnencryptedDOH newconfig.FilterHandler = applyAdditionalFiltering newconfig.GetCustomUpstreamByClient = Context.clients.FindUpstreams - return newconfig + + return newconfig, nil +} + +func newDNSCrypt(bindHost net.IP, tlsConf tlsConfigSettings) (dnscc dnsforward.DNSCryptConfig, err error) { + if tlsConf.DNSCryptConfigFile == "" { + return dnscc, agherr.Error("no dnscrypt_config_file") + } + + f, err := os.Open(tlsConf.DNSCryptConfigFile) + if err != nil { + return dnscc, fmt.Errorf("opening dnscrypt config: %w", err) + } + defer f.Close() + + rc := &dnscrypt.ResolverConfig{} + err = yaml.NewDecoder(f).Decode(rc) + if err != nil { + return dnscc, fmt.Errorf("decoding dnscrypt config: %w", err) + } + + cert, err := rc.CreateCert() + if err != nil { + return dnscc, fmt.Errorf("creating dnscrypt cert: %w", err) + } + + udpAddr := &net.UDPAddr{ + IP: bindHost, + Port: tlsConf.PortDNSCrypt, + } + tcpAddr := &net.TCPAddr{ + IP: bindHost, + Port: tlsConf.PortDNSCrypt, + } + + return dnsforward.DNSCryptConfig{ + UDPListenAddr: udpAddr, + TCPListenAddr: tcpAddr, + ResolverCert: cert, + ProviderName: rc.ProviderName, + Enabled: true, + }, nil } type dnsEncryption struct { @@ -281,11 +342,16 @@ func startDNSServer() error { return nil } -func reconfigureDNSServer() error { - newconfig := generateServerConfig() - err := Context.dnsServer.Reconfigure(&newconfig) +func reconfigureDNSServer() (err error) { + var newconfig dnsforward.ServerConfig + newconfig, err = generateServerConfig() if err != nil { - return fmt.Errorf("couldn't start forwarding DNS server: %w", err) + return fmt.Errorf("generating forwarding dns server config: %w", err) + } + + err = Context.dnsServer.Reconfigure(&newconfig) + if err != nil { + return fmt.Errorf("starting forwarding dns server: %w", err) } return nil