diff --git a/CHANGELOG.md b/CHANGELOG.md index 995783ea..ad463ee4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to ### Added +- The ability to serve DNS queries on multiple hosts and interfaces ([#1401]). - `ips` and `text` DHCP server options ([#2385]). - `SRV` records support in `$dnsrewrite` filters ([#2533]). @@ -38,6 +39,7 @@ and this project adheres to - Go 1.14 support. +[#1401]: https://github.com/AdguardTeam/AdGuardHome/issues/1401 [#2385]: https://github.com/AdguardTeam/AdGuardHome/issues/2385 [#2412]: https://github.com/AdguardTeam/AdGuardHome/issues/2412 [#2498]: https://github.com/AdguardTeam/AdGuardHome/issues/2498 diff --git a/internal/dnsfilter/dnsfilter.go b/internal/dnsfilter/dnsfilter.go index 0e05de1a..3c1dfccc 100644 --- a/internal/dnsfilter/dnsfilter.go +++ b/internal/dnsfilter/dnsfilter.go @@ -50,10 +50,9 @@ type Resolver interface { // Config allows you to configure DNS filtering with New() or just change variables directly. type Config struct { - ParentalEnabled bool `yaml:"parental_enabled"` - SafeSearchEnabled bool `yaml:"safesearch_enabled"` - SafeBrowsingEnabled bool `yaml:"safebrowsing_enabled"` - ResolverAddress string `yaml:"-"` // DNS server address + ParentalEnabled bool `yaml:"parental_enabled"` + SafeSearchEnabled bool `yaml:"safesearch_enabled"` + SafeBrowsingEnabled bool `yaml:"safebrowsing_enabled"` SafeBrowsingCacheSize uint `yaml:"safebrowsing_cache_size"` // (in bytes) SafeSearchCacheSize uint `yaml:"safesearch_cache_size"` // (in bytes) diff --git a/internal/dnsforward/config.go b/internal/dnsforward/config.go index c3b9b20f..e80ac941 100644 --- a/internal/dnsforward/config.go +++ b/internal/dnsforward/config.go @@ -93,8 +93,8 @@ type FilteringConfig struct { // TLSConfig is the TLS configuration for HTTPS, DNS-over-HTTPS, and DNS-over-TLS type TLSConfig struct { - TLSListenAddr *net.TCPAddr `yaml:"-" json:"-"` - QUICListenAddr *net.UDPAddr `yaml:"-" json:"-"` + TLSListenAddrs []*net.TCPAddr `yaml:"-" json:"-"` + QUICListenAddrs []*net.UDPAddr `yaml:"-" json:"-"` // Reject connection if the client uses server name (in SNI) that doesn't match the certificate StrictSNICheck bool `yaml:"strict_sni_check" json:"-"` @@ -121,18 +121,18 @@ type TLSConfig struct { // DNSCryptConfig is the DNSCrypt server configuration struct. type DNSCryptConfig struct { - UDPListenAddr *net.UDPAddr - TCPListenAddr *net.TCPAddr - ProviderName string - ResolverCert *dnscrypt.Cert - Enabled bool + UDPListenAddrs []*net.UDPAddr + TCPListenAddrs []*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 { - UDPListenAddr *net.UDPAddr // UDP listen address - TCPListenAddr *net.TCPAddr // TCP listen address + UDPListenAddrs []*net.UDPAddr // UDP listen address + TCPListenAddrs []*net.TCPAddr // TCP listen address UpstreamConfig *proxy.UpstreamConfig // Upstream DNS servers config OnDNSRequest func(d *proxy.DNSContext) @@ -153,16 +153,16 @@ type ServerConfig struct { // if any of ServerConfig values are zero, then default values from below are used var defaultValues = ServerConfig{ - UDPListenAddr: &net.UDPAddr{Port: 53}, - TCPListenAddr: &net.TCPAddr{Port: 53}, + UDPListenAddrs: []*net.UDPAddr{{Port: 53}}, + TCPListenAddrs: []*net.TCPAddr{{Port: 53}}, FilteringConfig: FilteringConfig{BlockedResponseTTL: 3600}, } // createProxyConfig creates and validates configuration for the main proxy func (s *Server) createProxyConfig() (proxy.Config, error) { proxyConfig := proxy.Config{ - UDPListenAddr: []*net.UDPAddr{s.conf.UDPListenAddr}, - TCPListenAddr: []*net.TCPAddr{s.conf.TCPListenAddr}, + UDPListenAddr: s.conf.UDPListenAddrs, + TCPListenAddr: s.conf.TCPListenAddrs, Ratelimit: int(s.conf.Ratelimit), RatelimitWhitelist: s.conf.RatelimitWhitelist, RefuseAny: s.conf.RefuseAny, @@ -205,8 +205,8 @@ func (s *Server) createProxyConfig() (proxy.Config, error) { } if s.conf.DNSCryptConfig.Enabled { - proxyConfig.DNSCryptUDPListenAddr = []*net.UDPAddr{s.conf.DNSCryptConfig.UDPListenAddr} - proxyConfig.DNSCryptTCPListenAddr = []*net.TCPAddr{s.conf.DNSCryptConfig.TCPListenAddr} + proxyConfig.DNSCryptUDPListenAddr = s.conf.DNSCryptConfig.UDPListenAddrs + proxyConfig.DNSCryptTCPListenAddr = s.conf.DNSCryptConfig.TCPListenAddrs proxyConfig.DNSCryptProviderName = s.conf.DNSCryptConfig.ProviderName proxyConfig.DNSCryptResolverCert = s.conf.DNSCryptConfig.ResolverCert } @@ -225,21 +225,27 @@ func (s *Server) initDefaultSettings() { if len(s.conf.UpstreamDNS) == 0 { s.conf.UpstreamDNS = defaultDNS } + if len(s.conf.BootstrapDNS) == 0 { s.conf.BootstrapDNS = defaultBootstrap } + if len(s.conf.ParentalBlockHost) == 0 { s.conf.ParentalBlockHost = parentalBlockHost } + if len(s.conf.SafeBrowsingBlockHost) == 0 { s.conf.SafeBrowsingBlockHost = safeBrowsingBlockHost } - if s.conf.UDPListenAddr == nil { - s.conf.UDPListenAddr = defaultValues.UDPListenAddr + + if s.conf.UDPListenAddrs == nil { + s.conf.UDPListenAddrs = defaultValues.UDPListenAddrs } - if s.conf.TCPListenAddr == nil { - s.conf.TCPListenAddr = defaultValues.TCPListenAddr + + if s.conf.TCPListenAddrs == nil { + s.conf.TCPListenAddrs = defaultValues.TCPListenAddrs } + if len(s.conf.BlockedHosts) == 0 { s.conf.BlockedHosts = defaultBlockedHosts } @@ -325,17 +331,16 @@ func (s *Server) prepareTLS(proxyConfig *proxy.Config) error { return nil } - if s.conf.TLSListenAddr == nil && - s.conf.QUICListenAddr == nil { + if s.conf.TLSListenAddrs == nil && s.conf.QUICListenAddrs == nil { return nil } - if s.conf.TLSListenAddr != nil { - proxyConfig.TLSListenAddr = []*net.TCPAddr{s.conf.TLSListenAddr} + if s.conf.TLSListenAddrs != nil { + proxyConfig.TLSListenAddr = s.conf.TLSListenAddrs } - if s.conf.QUICListenAddr != nil { - proxyConfig.QUICListenAddr = []*net.UDPAddr{s.conf.QUICListenAddr} + if s.conf.QUICListenAddrs != nil { + proxyConfig.QUICListenAddr = s.conf.QUICListenAddrs } var err error diff --git a/internal/dnsforward/dnsforward_test.go b/internal/dnsforward/dnsforward_test.go index 555ca047..a1237849 100644 --- a/internal/dnsforward/dnsforward_test.go +++ b/internal/dnsforward/dnsforward_test.go @@ -123,8 +123,8 @@ func createTestTLS(t *testing.T, tlsConf TLSConfig) (s *Server, certPem []byte) _, certPem, keyPem = createServerTLSConfig(t) s = createTestServer(t, &dnsfilter.Config{}, ServerConfig{ - UDPListenAddr: &net.UDPAddr{}, - TCPListenAddr: &net.TCPAddr{}, + UDPListenAddrs: []*net.UDPAddr{{}}, + TCPListenAddrs: []*net.TCPAddr{{}}, }) tlsConf.CertificateChainData, tlsConf.PrivateKeyData = certPem, keyPem @@ -219,8 +219,8 @@ func sendTestMessages(t *testing.T, conn *dns.Conn) { func TestServer(t *testing.T) { s := createTestServer(t, &dnsfilter.Config{}, ServerConfig{ - UDPListenAddr: &net.UDPAddr{}, - TCPListenAddr: &net.TCPAddr{}, + UDPListenAddrs: []*net.UDPAddr{{}}, + TCPListenAddrs: []*net.TCPAddr{{}}, }) s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{ &aghtest.TestUpstream{ @@ -257,8 +257,8 @@ func TestServer(t *testing.T) { func TestServerWithProtectionDisabled(t *testing.T) { s := createTestServer(t, &dnsfilter.Config{}, ServerConfig{ - UDPListenAddr: &net.UDPAddr{}, - TCPListenAddr: &net.TCPAddr{}, + UDPListenAddrs: []*net.UDPAddr{{}}, + TCPListenAddrs: []*net.TCPAddr{{}}, }) s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{ &aghtest.TestUpstream{ @@ -281,7 +281,7 @@ func TestServerWithProtectionDisabled(t *testing.T) { func TestDoTServer(t *testing.T) { s, certPem := createTestTLS(t, TLSConfig{ - TLSListenAddr: &net.TCPAddr{}, + TLSListenAddrs: []*net.TCPAddr{{}}, }) s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{ &aghtest.TestUpstream{ @@ -311,7 +311,7 @@ func TestDoTServer(t *testing.T) { func TestDoQServer(t *testing.T) { s, _ := createTestTLS(t, TLSConfig{ - QUICListenAddr: &net.UDPAddr{}, + QUICListenAddrs: []*net.UDPAddr{{}}, }) s.conf.UpstreamConfig.Upstreams = []upstream.Upstream{ &aghtest.TestUpstream{ @@ -348,8 +348,8 @@ func TestServerRace(t *testing.T) { CacheTime: 30, } forwardConf := ServerConfig{ - UDPListenAddr: &net.UDPAddr{}, - TCPListenAddr: &net.TCPAddr{}, + UDPListenAddrs: []*net.UDPAddr{{}}, + TCPListenAddrs: []*net.TCPAddr{{}}, FilteringConfig: FilteringConfig{ ProtectionEnabled: true, UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"}, @@ -383,8 +383,8 @@ func TestSafeSearch(t *testing.T) { CustomResolver: resolver, } forwardConf := ServerConfig{ - UDPListenAddr: &net.UDPAddr{}, - TCPListenAddr: &net.TCPAddr{}, + UDPListenAddrs: []*net.UDPAddr{{}}, + TCPListenAddrs: []*net.TCPAddr{{}}, FilteringConfig: FilteringConfig{ ProtectionEnabled: true, }, @@ -440,8 +440,8 @@ func TestSafeSearch(t *testing.T) { func TestInvalidRequest(t *testing.T) { s := createTestServer(t, &dnsfilter.Config{}, ServerConfig{ - UDPListenAddr: &net.UDPAddr{}, - TCPListenAddr: &net.TCPAddr{}, + UDPListenAddrs: []*net.UDPAddr{{}}, + TCPListenAddrs: []*net.TCPAddr{{}}, }) startDeferStop(t, s) @@ -464,8 +464,8 @@ func TestInvalidRequest(t *testing.T) { func TestBlockedRequest(t *testing.T) { forwardConf := ServerConfig{ - UDPListenAddr: &net.UDPAddr{}, - TCPListenAddr: &net.TCPAddr{}, + UDPListenAddrs: []*net.UDPAddr{{}}, + TCPListenAddrs: []*net.TCPAddr{{}}, FilteringConfig: FilteringConfig{ ProtectionEnabled: true, }, @@ -488,8 +488,8 @@ func TestBlockedRequest(t *testing.T) { func TestServerCustomClientUpstream(t *testing.T) { forwardConf := ServerConfig{ - UDPListenAddr: &net.UDPAddr{}, - TCPListenAddr: &net.TCPAddr{}, + UDPListenAddrs: []*net.UDPAddr{{}}, + TCPListenAddrs: []*net.TCPAddr{{}}, FilteringConfig: FilteringConfig{ ProtectionEnabled: true, }, @@ -537,8 +537,8 @@ var testIPv4 = map[string][]net.IP{ func TestBlockCNAMEProtectionEnabled(t *testing.T) { s := createTestServer(t, &dnsfilter.Config{}, ServerConfig{ - UDPListenAddr: &net.UDPAddr{}, - TCPListenAddr: &net.TCPAddr{}, + UDPListenAddrs: []*net.UDPAddr{{}}, + TCPListenAddrs: []*net.TCPAddr{{}}, }) testUpstm := &aghtest.TestUpstream{ CName: testCNAMEs, @@ -564,8 +564,8 @@ func TestBlockCNAMEProtectionEnabled(t *testing.T) { func TestBlockCNAME(t *testing.T) { forwardConf := ServerConfig{ - UDPListenAddr: &net.UDPAddr{}, - TCPListenAddr: &net.TCPAddr{}, + UDPListenAddrs: []*net.UDPAddr{{}}, + TCPListenAddrs: []*net.TCPAddr{{}}, FilteringConfig: FilteringConfig{ ProtectionEnabled: true, }, @@ -622,8 +622,8 @@ func TestBlockCNAME(t *testing.T) { func TestClientRulesForCNAMEMatching(t *testing.T) { forwardConf := ServerConfig{ - UDPListenAddr: &net.UDPAddr{}, - TCPListenAddr: &net.TCPAddr{}, + UDPListenAddrs: []*net.UDPAddr{{}}, + TCPListenAddrs: []*net.TCPAddr{{}}, FilteringConfig: FilteringConfig{ ProtectionEnabled: true, FilterHandler: func(_ net.IP, _ string, settings *dnsfilter.RequestFilteringSettings) { @@ -664,8 +664,8 @@ func TestClientRulesForCNAMEMatching(t *testing.T) { func TestNullBlockedRequest(t *testing.T) { forwardConf := ServerConfig{ - UDPListenAddr: &net.UDPAddr{}, - TCPListenAddr: &net.TCPAddr{}, + UDPListenAddrs: []*net.UDPAddr{{}}, + TCPListenAddrs: []*net.TCPAddr{{}}, FilteringConfig: FilteringConfig{ ProtectionEnabled: true, BlockingMode: "null_ip", @@ -707,8 +707,8 @@ func TestBlockedCustomIP(t *testing.T) { DNSFilter: dnsfilter.New(&dnsfilter.Config{}, filters), }) conf := &ServerConfig{ - UDPListenAddr: &net.UDPAddr{}, - TCPListenAddr: &net.TCPAddr{}, + UDPListenAddrs: []*net.UDPAddr{{}}, + TCPListenAddrs: []*net.TCPAddr{{}}, FilteringConfig: FilteringConfig{ ProtectionEnabled: true, BlockingMode: "custom_ip", @@ -746,8 +746,8 @@ func TestBlockedCustomIP(t *testing.T) { func TestBlockedByHosts(t *testing.T) { forwardConf := ServerConfig{ - UDPListenAddr: &net.UDPAddr{}, - TCPListenAddr: &net.TCPAddr{}, + UDPListenAddrs: []*net.UDPAddr{{}}, + TCPListenAddrs: []*net.TCPAddr{{}}, FilteringConfig: FilteringConfig{ ProtectionEnabled: true, }, @@ -780,8 +780,8 @@ func TestBlockedBySafeBrowsing(t *testing.T) { SafeBrowsingEnabled: true, } forwardConf := ServerConfig{ - UDPListenAddr: &net.UDPAddr{}, - TCPListenAddr: &net.TCPAddr{}, + UDPListenAddrs: []*net.UDPAddr{{}}, + TCPListenAddrs: []*net.TCPAddr{{}}, FilteringConfig: FilteringConfig{ SafeBrowsingBlockHost: ans4.String(), ProtectionEnabled: true, @@ -824,8 +824,8 @@ func TestRewrite(t *testing.T) { s := NewServer(DNSCreateParams{DNSFilter: f}) assert.Nil(t, s.Prepare(&ServerConfig{ - UDPListenAddr: &net.UDPAddr{}, - TCPListenAddr: &net.TCPAddr{}, + UDPListenAddrs: []*net.UDPAddr{{}}, + TCPListenAddrs: []*net.TCPAddr{{}}, FilteringConfig: FilteringConfig{ ProtectionEnabled: true, UpstreamDNS: []string{"8.8.8.8:53"}, @@ -1109,8 +1109,8 @@ func TestPTRResponseFromDHCPLeases(t *testing.T) { DHCPServer: &testDHCP{}, }) - s.conf.UDPListenAddr = &net.UDPAddr{} - s.conf.TCPListenAddr = &net.TCPAddr{} + s.conf.UDPListenAddrs = []*net.UDPAddr{{}} + s.conf.TCPListenAddrs = []*net.TCPAddr{{}} s.conf.UpstreamDNS = []string{"127.0.0.1:53"} s.conf.FilteringConfig.ProtectionEnabled = true require.Nil(t, s.Prepare(nil)) @@ -1154,8 +1154,8 @@ func TestPTRResponseFromHosts(t *testing.T) { t.Cleanup(c.AutoHosts.Close) s := NewServer(DNSCreateParams{DNSFilter: dnsfilter.New(&c, nil)}) - s.conf.UDPListenAddr = &net.UDPAddr{} - s.conf.TCPListenAddr = &net.TCPAddr{} + s.conf.UDPListenAddrs = []*net.UDPAddr{{}} + s.conf.TCPListenAddrs = []*net.TCPAddr{{}} s.conf.UpstreamDNS = []string{"127.0.0.1:53"} s.conf.FilteringConfig.ProtectionEnabled = true require.Nil(t, s.Prepare(nil)) diff --git a/internal/dnsforward/http_test.go b/internal/dnsforward/http_test.go index 587d3587..b55f759a 100644 --- a/internal/dnsforward/http_test.go +++ b/internal/dnsforward/http_test.go @@ -23,8 +23,8 @@ func TestDNSForwardHTTTP_handleGetConfig(t *testing.T) { CacheTime: 30, } forwardConf := ServerConfig{ - UDPListenAddr: &net.UDPAddr{}, - TCPListenAddr: &net.TCPAddr{}, + UDPListenAddrs: []*net.UDPAddr{}, + TCPListenAddrs: []*net.TCPAddr{}, FilteringConfig: FilteringConfig{ ProtectionEnabled: true, UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"}, @@ -94,8 +94,8 @@ func TestDNSForwardHTTTP_handleSetConfig(t *testing.T) { CacheTime: 30, } forwardConf := ServerConfig{ - UDPListenAddr: &net.UDPAddr{}, - TCPListenAddr: &net.TCPAddr{}, + UDPListenAddrs: []*net.UDPAddr{}, + TCPListenAddrs: []*net.TCPAddr{}, FilteringConfig: FilteringConfig{ ProtectionEnabled: true, UpstreamDNS: []string{"8.8.8.8:53", "8.8.4.4:53"}, diff --git a/internal/home/config.go b/internal/home/config.go index 65a9401c..6bb87c52 100644 --- a/internal/home/config.go +++ b/internal/home/config.go @@ -76,8 +76,8 @@ type configuration struct { // field ordering is important -- yaml fields will mirror ordering from here type dnsConfig struct { - BindHost net.IP `yaml:"bind_host"` - Port int `yaml:"port"` + BindHosts []net.IP `yaml:"bind_hosts"` + Port int `yaml:"port"` // time interval for statistics (in days) StatsInterval uint32 `yaml:"statistics_interval"` @@ -101,7 +101,7 @@ type tlsConfigSettings struct { ForceHTTPS bool `yaml:"force_https" json:"force_https,omitempty"` // ForceHTTPS: if true, forces HTTP->HTTPS redirect PortHTTPS int `yaml:"port_https" json:"port_https,omitempty"` // HTTPS port. If 0, HTTPS will be disabled 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 + PortDNSOverQUIC int `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. @@ -125,7 +125,7 @@ var config = configuration{ BetaBindPort: 0, BindHost: net.IP{0, 0, 0, 0}, DNS: dnsConfig{ - BindHost: net.IP{0, 0, 0, 0}, + BindHosts: []net.IP{{0, 0, 0, 0}}, Port: 53, StatsInterval: 1, FilteringConfig: dnsforward.FilteringConfig{ diff --git a/internal/home/controlinstall.go b/internal/home/controlinstall.go index be1dc8ab..21ec4fa2 100644 --- a/internal/home/controlinstall.go +++ b/internal/home/controlinstall.go @@ -17,7 +17,6 @@ import ( "github.com/AdguardTeam/AdGuardHome/internal/agherr" "github.com/AdguardTeam/AdGuardHome/internal/aghnet" - "github.com/AdguardTeam/golibs/log" ) @@ -264,7 +263,7 @@ func copyInstallSettings(dst, src *configuration) { dst.BindHost = src.BindHost dst.BindPort = src.BindPort dst.BetaBindPort = src.BetaBindPort - dst.DNS.BindHost = src.DNS.BindHost + dst.DNS.BindHosts = src.DNS.BindHosts dst.DNS.Port = src.DNS.Port } @@ -335,7 +334,7 @@ func (web *Web) handleInstallConfigure(w http.ResponseWriter, r *http.Request) { Context.firstRun = false config.BindHost = newSettings.Web.IP config.BindPort = newSettings.Web.Port - config.DNS.BindHost = newSettings.DNS.IP + config.DNS.BindHosts = []net.IP{newSettings.DNS.IP} config.DNS.Port = newSettings.DNS.Port // TODO(e.burkov): StartMods() should be put in a separate goroutine at diff --git a/internal/home/dns.go b/internal/home/dns.go index 0020c77c..9692dbc4 100644 --- a/internal/home/dns.go +++ b/internal/home/dns.go @@ -56,11 +56,6 @@ func initDNSServer() error { Context.queryLog = querylog.New(conf) filterConf := config.DNS.DnsfilterConf - bindhost := config.DNS.BindHost - if config.DNS.BindHost.IsUnspecified() { - bindhost = net.IPv4(127, 0, 0, 1) - } - filterConf.ResolverAddress = net.JoinHostPort(bindhost.String(), strconv.Itoa(config.DNS.Port)) filterConf.AutoHosts = &Context.autoHosts filterConf.ConfigModified = onConfigModified filterConf.HTTPRegister = httpRegister @@ -114,11 +109,51 @@ func onDNSRequest(d *proxy.DNSContext) { } } -func generateServerConfig() (newconfig dnsforward.ServerConfig, err error) { - newconfig = dnsforward.ServerConfig{ - UDPListenAddr: &net.UDPAddr{IP: config.DNS.BindHost, Port: config.DNS.Port}, - TCPListenAddr: &net.TCPAddr{IP: config.DNS.BindHost, Port: config.DNS.Port}, - FilteringConfig: config.DNS.FilteringConfig, +func ipsToTCPAddrs(ips []net.IP, port int) (tcpAddrs []*net.TCPAddr) { + if ips == nil { + return nil + } + + tcpAddrs = make([]*net.TCPAddr, len(ips)) + for i, ip := range ips { + tcpAddrs[i] = &net.TCPAddr{ + IP: ip, + Port: port, + } + } + + return tcpAddrs +} + +func ipsToUDPAddrs(ips []net.IP, port int) (udpAddrs []*net.UDPAddr) { + if ips == nil { + return nil + } + + udpAddrs = make([]*net.UDPAddr, len(ips)) + for i, ip := range ips { + udpAddrs[i] = &net.UDPAddr{ + IP: ip, + Port: port, + } + } + + return udpAddrs +} + +func generateServerConfig() (newConf dnsforward.ServerConfig, err error) { + dnsConf := config.DNS + hosts := dnsConf.BindHosts + for i, h := range hosts { + if h.IsUnspecified() { + hosts[i] = net.IP{127, 0, 0, 1} + } + } + + newConf = dnsforward.ServerConfig{ + UDPListenAddrs: ipsToUDPAddrs(hosts, dnsConf.Port), + TCPListenAddrs: ipsToTCPAddrs(hosts, dnsConf.Port), + FilteringConfig: dnsConf.FilteringConfig, ConfigModified: onConfigModified, HTTPRegister: httpRegister, OnDNSRequest: onDNSRequest, @@ -127,25 +162,19 @@ func generateServerConfig() (newconfig dnsforward.ServerConfig, err error) { tlsConf := tlsConfigSettings{} Context.tls.WriteDiskConfig(&tlsConf) if tlsConf.Enabled { - newconfig.TLSConfig = tlsConf.TLSConfig - newconfig.TLSConfig.ServerName = tlsConf.ServerName + newConf.TLSConfig = tlsConf.TLSConfig + newConf.TLSConfig.ServerName = tlsConf.ServerName if tlsConf.PortDNSOverTLS != 0 { - newconfig.TLSListenAddr = &net.TCPAddr{ - IP: config.DNS.BindHost, - Port: tlsConf.PortDNSOverTLS, - } + newConf.TLSListenAddrs = ipsToTCPAddrs(hosts, tlsConf.PortDNSOverTLS) } if tlsConf.PortDNSOverQUIC != 0 { - newconfig.QUICListenAddr = &net.UDPAddr{ - IP: config.DNS.BindHost, - Port: int(tlsConf.PortDNSOverQUIC), - } + newConf.QUICListenAddrs = ipsToUDPAddrs(hosts, tlsConf.PortDNSOverQUIC) } if tlsConf.PortDNSCrypt != 0 { - newconfig.DNSCryptConfig, err = newDNSCrypt(config.DNS.BindHost, tlsConf) + newConf.DNSCryptConfig, err = newDNSCrypt(hosts, tlsConf) if err != nil { // Don't wrap the error, because it's already // wrapped by newDNSCrypt. @@ -154,17 +183,17 @@ func generateServerConfig() (newconfig dnsforward.ServerConfig, err error) { } } - newconfig.TLSv12Roots = Context.tlsRoots - newconfig.TLSCiphers = Context.tlsCiphers - newconfig.TLSAllowUnencryptedDOH = tlsConf.AllowUnencryptedDOH + newConf.TLSv12Roots = Context.tlsRoots + newConf.TLSCiphers = Context.tlsCiphers + newConf.TLSAllowUnencryptedDOH = tlsConf.AllowUnencryptedDOH - newconfig.FilterHandler = applyAdditionalFiltering - newconfig.GetCustomUpstreamByClient = Context.clients.FindUpstreams + newConf.FilterHandler = applyAdditionalFiltering + newConf.GetCustomUpstreamByClient = Context.clients.FindUpstreams - return newconfig, nil + return newConf, nil } -func newDNSCrypt(bindHost net.IP, tlsConf tlsConfigSettings) (dnscc dnsforward.DNSCryptConfig, err error) { +func newDNSCrypt(hosts []net.IP, tlsConf tlsConfigSettings) (dnscc dnsforward.DNSCryptConfig, err error) { if tlsConf.DNSCryptConfigFile == "" { return dnscc, agherr.Error("no dnscrypt_config_file") } @@ -186,21 +215,12 @@ func newDNSCrypt(bindHost net.IP, tlsConf tlsConfigSettings) (dnscc dnsforward.D 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, + UDPListenAddrs: ipsToUDPAddrs(hosts, tlsConf.PortDNSCrypt), + TCPListenAddrs: ipsToTCPAddrs(hosts, tlsConf.PortDNSCrypt), + ResolverCert: cert, + ProviderName: rc.ProviderName, + Enabled: true, }, nil } @@ -249,10 +269,8 @@ func getDNSEncryption() (de dnsEncryption) { } // Get the list of DNS addresses the server is listening on -func getDNSAddresses() []string { - dnsAddresses := []string{} - - if config.DNS.BindHost.IsUnspecified() { +func getDNSAddresses() (dnsAddrs []string) { + if hosts := config.DNS.BindHosts; len(hosts) == 0 || hosts[0].IsUnspecified() { ifaces, e := aghnet.GetValidNetInterfacesForWeb() if e != nil { log.Error("Couldn't get network interfaces: %v", e) @@ -261,25 +279,29 @@ func getDNSAddresses() []string { for _, iface := range ifaces { for _, addr := range iface.Addresses { - addDNSAddress(&dnsAddresses, addr) + addDNSAddress(&dnsAddrs, addr) } } } else { - addDNSAddress(&dnsAddresses, config.DNS.BindHost) + for _, h := range hosts { + addDNSAddress(&dnsAddrs, h) + } } de := getDNSEncryption() if de.https != "" { - dnsAddresses = append(dnsAddresses, de.https) - } - if de.tls != "" { - dnsAddresses = append(dnsAddresses, de.tls) - } - if de.quic != "" { - dnsAddresses = append(dnsAddresses, de.quic) + dnsAddrs = append(dnsAddrs, de.https) } - return dnsAddresses + if de.tls != "" { + dnsAddrs = append(dnsAddrs, de.tls) + } + + if de.quic != "" { + dnsAddrs = append(dnsAddrs, de.quic) + } + + return dnsAddrs } // applyAdditionalFiltering adds additional client information and settings if @@ -353,13 +375,13 @@ func startDNSServer() error { } func reconfigureDNSServer() (err error) { - var newconfig dnsforward.ServerConfig - newconfig, err = generateServerConfig() + var newConf dnsforward.ServerConfig + newConf, err = generateServerConfig() if err != nil { return fmt.Errorf("generating forwarding dns server config: %w", err) } - err = Context.dnsServer.Reconfigure(&newconfig) + err = Context.dnsServer.Reconfigure(&newConf) if err != nil { return fmt.Errorf("starting forwarding dns server: %w", err) } diff --git a/internal/home/upgrade.go b/internal/home/upgrade.go index d92a4185..c1bb0609 100644 --- a/internal/home/upgrade.go +++ b/internal/home/upgrade.go @@ -13,12 +13,20 @@ import ( yaml "gopkg.in/yaml.v2" ) -const currentSchemaVersion = 7 // used for upgrading from old configs to new config +// currentSchemaVersion is the current schema version. +const currentSchemaVersion = 8 + +// These aliases are provided for convenience. +type ( + any = interface{} + yarr = []any + yobj = map[any]any +) // Performs necessary upgrade operations if needed func upgradeConfig() error { // read a config file into an interface map, so we can manipulate values without losing any - diskConfig := map[string]interface{}{} + diskConfig := yobj{} body, err := readConfigFile() if err != nil { return err @@ -53,7 +61,7 @@ func upgradeConfig() error { } // Upgrade from oldVersion to newVersion -func upgradeConfigSchema(oldVersion int, diskConfig *map[string]interface{}) error { +func upgradeConfigSchema(oldVersion int, diskConfig *yobj) error { switch oldVersion { case 0: err := upgradeSchema0to1(diskConfig) @@ -96,6 +104,11 @@ func upgradeConfigSchema(oldVersion int, diskConfig *map[string]interface{}) err if err != nil { return err } + case 7: + err := upgradeSchema7to8(diskConfig) + if err != nil { + return err + } default: err := fmt.Errorf("configuration file contains unknown schema_version, abort") log.Println(err) @@ -121,7 +134,7 @@ func upgradeConfigSchema(oldVersion int, diskConfig *map[string]interface{}) err // The first schema upgrade: // No more "dnsfilter.txt", filters are now kept in data/filters/ -func upgradeSchema0to1(diskConfig *map[string]interface{}) error { +func upgradeSchema0to1(diskConfig *yobj) error { log.Printf("%s(): called", funcName()) dnsFilterPath := filepath.Join(Context.workDir, "dnsfilter.txt") @@ -142,7 +155,7 @@ func upgradeSchema0to1(diskConfig *map[string]interface{}) error { // Second schema upgrade: // coredns is now dns in config // delete 'Corefile', since we don't use that anymore -func upgradeSchema1to2(diskConfig *map[string]interface{}) error { +func upgradeSchema1to2(diskConfig *yobj) error { log.Printf("%s(): called", funcName()) coreFilePath := filepath.Join(Context.workDir, "Corefile") @@ -166,7 +179,7 @@ func upgradeSchema1to2(diskConfig *map[string]interface{}) error { // Third schema upgrade: // Bootstrap DNS becomes an array -func upgradeSchema2to3(diskConfig *map[string]interface{}) error { +func upgradeSchema2to3(diskConfig *yobj) error { log.Printf("%s(): called", funcName()) // Let's read dns configuration from diskConfig @@ -175,8 +188,8 @@ func upgradeSchema2to3(diskConfig *map[string]interface{}) error { return fmt.Errorf("no DNS configuration in config file") } - // Convert interface{} to map[string]interface{} - newDNSConfig := make(map[string]interface{}) + // Convert interface{} to yobj + newDNSConfig := make(yobj) switch v := dnsConfig.(type) { case map[interface{}]interface{}: @@ -204,7 +217,7 @@ func upgradeSchema2to3(diskConfig *map[string]interface{}) error { } // Add use_global_blocked_services=true setting for existing "clients" array -func upgradeSchema3to4(diskConfig *map[string]interface{}) error { +func upgradeSchema3to4(diskConfig *yobj) error { log.Printf("%s(): called", funcName()) (*diskConfig)["schema_version"] = 4 @@ -240,7 +253,7 @@ func upgradeSchema3to4(diskConfig *map[string]interface{}) error { // - name: "..." // password: "..." // ... -func upgradeSchema4to5(diskConfig *map[string]interface{}) error { +func upgradeSchema4to5(diskConfig *yobj) error { log.Printf("%s(): called", funcName()) (*diskConfig)["schema_version"] = 5 @@ -295,7 +308,7 @@ func upgradeSchema4to5(diskConfig *map[string]interface{}) error { // ids: // - 127.0.0.1 // - ... -func upgradeSchema5to6(diskConfig *map[string]interface{}) error { +func upgradeSchema5to6(diskConfig *yobj) error { log.Printf("%s(): called", funcName()) (*diskConfig)["schema_version"] = 6 @@ -365,7 +378,7 @@ func upgradeSchema5to6(diskConfig *map[string]interface{}) error { // dhcpv4: // gateway_ip: 192.168.56.1 // ... -func upgradeSchema6to7(diskConfig *map[string]interface{}) error { +func upgradeSchema6to7(diskConfig *yobj) error { log.Printf("Upgrade yaml: 6 to 7") (*diskConfig)["schema_version"] = 7 @@ -384,7 +397,7 @@ func upgradeSchema6to7(diskConfig *map[string]interface{}) error { return nil } - dhcpv4 := map[string]interface{}{ + dhcpv4 := yobj{ "gateway_ip": str, } delete(dhcp, "gateway_ip") @@ -438,6 +451,44 @@ func upgradeSchema6to7(diskConfig *map[string]interface{}) error { return nil } +// upgradeSchema7to8 performs the following changes: +// +// # BEFORE: +// 'dns': +// 'bind_host': '127.0.0.1' +// +// # AFTER: +// 'dns': +// 'bind_hosts': +// - '127.0.0.1' +// +func upgradeSchema7to8(diskConfig *yobj) (err error) { + log.Printf("Upgrade yaml: 7 to 8") + + (*diskConfig)["schema_version"] = 8 + + dnsVal, ok := (*diskConfig)["dns"] + if !ok { + return nil + } + + dns, ok := dnsVal.(yobj) + if !ok { + return fmt.Errorf("unexpected type of dns: %T", dnsVal) + } + + bindHostVal := dns["bind_host"] + bindHost, ok := bindHostVal.(string) + if !ok { + return fmt.Errorf("undexpected type of dns.bind_host: %T", bindHostVal) + } + + delete(dns, "bind_host") + dns["bind_hosts"] = yarr{bindHost} + + return nil +} + // TODO(a.garipov): Replace with log.Output when we port it to our logging // package. func funcName() string { diff --git a/internal/home/upgrade_test.go b/internal/home/upgrade_test.go index 4210305a..b1c9552a 100644 --- a/internal/home/upgrade_test.go +++ b/internal/home/upgrade_test.go @@ -1,18 +1,13 @@ package home import ( - "fmt" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -// any is a convenient alias for interface{}. -type any = interface{} - -// object is a convenient alias for map[string]interface{}. -type object = map[string]any +// TODO(a.garipov): Cover all migrations, use a testdata/ dir. func TestUpgradeSchema1to2(t *testing.T) { diskConf := testDiskConf(1) @@ -25,11 +20,10 @@ func TestUpgradeSchema1to2(t *testing.T) { _, ok := diskConf["coredns"] require.False(t, ok) - dnsMap, ok := diskConf["dns"] + newDNSConf, ok := diskConf["dns"] require.True(t, ok) - oldDNSConf := convertToObject(t, testDNSConf(1)) - newDNSConf := convertToObject(t, dnsMap) + oldDNSConf := testDNSConf(1) assert.Equal(t, oldDNSConf, newDNSConf) oldExcludedEntries := []string{"coredns", "schema_version"} @@ -49,7 +43,9 @@ func TestUpgradeSchema2to3(t *testing.T) { dnsMap, ok := diskConf["dns"] require.True(t, ok) - newDNSConf := convertToObject(t, dnsMap) + newDNSConf, ok := dnsMap.(yobj) + require.True(t, ok) + bootstrapDNS := newDNSConf["bootstrap_dns"] switch v := bootstrapDNS.(type) { case []string: @@ -60,7 +56,7 @@ func TestUpgradeSchema2to3(t *testing.T) { } excludedEntries := []string{"bootstrap_dns"} - oldDNSConf := convertToObject(t, testDNSConf(2)) + oldDNSConf := testDNSConf(2) assertEqualExcept(t, oldDNSConf, newDNSConf, excludedEntries, excludedEntries) excludedEntries = []string{"dns", "schema_version"} @@ -68,29 +64,34 @@ func TestUpgradeSchema2to3(t *testing.T) { assertEqualExcept(t, oldDiskConf, diskConf, excludedEntries, excludedEntries) } -func convertToObject(t *testing.T, oldConf any) (newConf object) { - t.Helper() - - switch v := oldConf.(type) { - case map[any]any: - newConf = make(object, len(v)) - for key, value := range v { - newConf[fmt.Sprint(key)] = value - } - case object: - newConf = make(object, len(v)) - for key, value := range v { - newConf[key] = value - } - default: - t.Fatalf("dns configuration is not a map, got %T", oldConf) +func TestUpgradeSchema7to8(t *testing.T) { + const host = "1.2.3.4" + oldConf := yobj{ + "dns": yobj{ + "bind_host": host, + }, + "schema_version": 7, } - return newConf + err := upgradeSchema7to8(&oldConf) + require.Nil(t, err) + + require.Equal(t, oldConf["schema_version"], 8) + + dnsVal, ok := oldConf["dns"] + require.True(t, ok) + + newDNSConf, ok := dnsVal.(yobj) + require.True(t, ok) + + newBindHosts, ok := newDNSConf["bind_hosts"].(yarr) + require.True(t, ok) + require.Len(t, newBindHosts, 1) + assert.Equal(t, host, newBindHosts[0]) } // assertEqualExcept removes entries from configs and compares them. -func assertEqualExcept(t *testing.T, oldConf, newConf object, oldKeys, newKeys []string) { +func assertEqualExcept(t *testing.T, oldConf, newConf yobj, oldKeys, newKeys []string) { t.Helper() for _, k := range oldKeys { @@ -103,20 +104,17 @@ func assertEqualExcept(t *testing.T, oldConf, newConf object, oldKeys, newKeys [ assert.Equal(t, oldConf, newConf) } -func testDiskConf(schemaVersion int) (diskConf object) { - filters := []filter{ - { - URL: "https://filters.adtidy.org/android/filters/111_optimized.txt", - Name: "Latvian filter", - RulesCount: 100, - }, - { - URL: "https://easylist.to/easylistgermany/easylistgermany.txt", - Name: "Germany filter", - RulesCount: 200, - }, - } - diskConf = object{ +func testDiskConf(schemaVersion int) (diskConf yobj) { + filters := []filter{{ + URL: "https://filters.adtidy.org/android/filters/111_optimized.txt", + Name: "Latvian filter", + RulesCount: 100, + }, { + URL: "https://easylist.to/easylistgermany/easylistgermany.txt", + Name: "Germany filter", + RulesCount: 200, + }} + diskConf = yobj{ "language": "en", "filters": filters, "user_rules": []string{}, @@ -139,8 +137,8 @@ func testDiskConf(schemaVersion int) (diskConf object) { // testDNSConf creates a DNS config for test the way gopkg.in/yaml.v2 would // unmarshal it. In YAML, keys aren't guaranteed to always only be strings. -func testDNSConf(schemaVersion int) (dnsConf map[any]any) { - dnsConf = map[any]any{ +func testDNSConf(schemaVersion int) (dnsConf yobj) { + dnsConf = yobj{ "port": 53, "blocked_response_ttl": 10, "querylog_enabled": true, diff --git a/scripts/make/go-build.sh b/scripts/make/go-build.sh index f8520f21..dbf6f7ac 100644 --- a/scripts/make/go-build.sh +++ b/scripts/make/go-build.sh @@ -93,7 +93,7 @@ readonly build_flags="${BUILD_FLAGS:-$out_flags $par_flags\ $v_flags $x_flags}" # Don't use quotes with flag variables to get word splitting. -"$go" generate $v_flags $x_flags ./... +"$go" generate $v_flags $x_flags ./main.go # Don't use quotes with flag variables to get word splitting. "$go" build --ldflags "$ldflags" $build_flags