From 5d0d32b926b1b437d2527024cdda6a795070bcd2 Mon Sep 17 00:00:00 2001
From: Ainar Garipov <a.garipov@adguard.com>
Date: Tue, 23 Mar 2021 12:32:07 +0300
Subject: [PATCH] Pull request: all: support multiple dns hosts

Updates #1401.

Squashed commit of the following:

commit a18c3f062a88ad7d7fbfacaedb893f1ca660b6dc
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Mar 22 21:55:26 2021 +0300

    home: imp code

commit 2b4a28cbf379fbc5fb168af6d8d078cab2b8bd64
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Mar 22 20:55:08 2021 +0300

    all: rm unused field

commit 5766a97dafff4acff6b909eb6303459f7991c81e
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Mon Mar 22 16:40:14 2021 +0300

    all: support multiple dns hosts
---
 CHANGELOG.md                           |   2 +
 internal/dnsfilter/dnsfilter.go        |   7 +-
 internal/dnsforward/config.go          |  55 +++++-----
 internal/dnsforward/dnsforward_test.go |  76 +++++++-------
 internal/dnsforward/http_test.go       |   8 +-
 internal/home/config.go                |   8 +-
 internal/home/controlinstall.go        |   5 +-
 internal/home/dns.go                   | 140 ++++++++++++++-----------
 internal/home/upgrade.go               |  77 +++++++++++---
 internal/home/upgrade_test.go          |  88 ++++++++--------
 scripts/make/go-build.sh               |   2 +-
 11 files changed, 272 insertions(+), 196 deletions(-)

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