Pull request 1978: 4923 gopacket dhcp vol.2
Merge in DNS/adguard-home from 4923-gopacket-dhcp-vol.2 to master
Updates #4923.
Squashed commit of the following:
commit d0ef7d44af9790ed55401f6f65c7149f4c3658f7
Merge: f92b4c72d a4fdc3e8e
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date: Wed Aug 30 13:43:41 2023 +0300
Merge branch 'master' into 4923-gopacket-dhcp-vol.2
commit f92b4c72de03ceacb9b8890b7cf4307688795ce5
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date: Mon Aug 28 12:33:29 2023 +0300
dhcpd: imp code
commit 63f0fce99a0343af2670943770cfef4694ae93ed
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date: Thu Aug 24 15:01:34 2023 +0300
all: imp dhcpd code
commit 563b43b4b5ab6c9c9046c7f09008ea3ef344f4e9
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date: Fri Jul 28 17:03:55 2023 +0300
dhcpd: imp indexing
commit 340d3efa90ac4d34ba3d18702692de0fbc0247be
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date: Fri Jul 28 16:22:43 2023 +0300
all: adapt current code
This commit is contained in:
@@ -15,7 +15,6 @@ import (
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/client"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dhcpd"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/querylog"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/rdns"
|
||||
@@ -48,19 +47,7 @@ var defaultBlockedHosts = []string{"version.bind", "id.server", "hostname.bind"}
|
||||
|
||||
var webRegistered bool
|
||||
|
||||
// hostToIPTable is a convenient type alias for tables of host names to an IP
|
||||
// address.
|
||||
//
|
||||
// TODO(e.burkov): Use the [DHCP] interface instead.
|
||||
type hostToIPTable = map[string]netip.Addr
|
||||
|
||||
// ipToHostTable is a convenient type alias for tables of IP addresses to their
|
||||
// host names. For example, for use with PTR queries.
|
||||
//
|
||||
// TODO(e.burkov): Use the [DHCP] interface instead.
|
||||
type ipToHostTable = map[netip.Addr]string
|
||||
|
||||
// DHCP is an interface for accessing DHCP lease data needed in this package.
|
||||
// DHCP is an interface for accesing DHCP lease data needed in this package.
|
||||
type DHCP interface {
|
||||
// HostByIP returns the hostname of the DHCP client with the given IP
|
||||
// address. The address will be netip.Addr{} if there is no such client,
|
||||
@@ -89,18 +76,34 @@ type DHCP interface {
|
||||
//
|
||||
// The zero Server is empty and ready for use.
|
||||
type Server struct {
|
||||
dnsProxy *proxy.Proxy // DNS proxy instance
|
||||
dnsFilter *filtering.DNSFilter // DNS filter instance
|
||||
dhcpServer dhcpd.Interface // DHCP server instance (optional)
|
||||
queryLog querylog.QueryLog // Query log instance
|
||||
stats stats.Interface
|
||||
access *accessManager
|
||||
// dnsProxy is the DNS proxy for forwarding client's DNS requests.
|
||||
dnsProxy *proxy.Proxy
|
||||
|
||||
// dnsFilter is the DNS filter for filtering client's DNS requests and
|
||||
// responses.
|
||||
dnsFilter *filtering.DNSFilter
|
||||
|
||||
// dhcpServer is the DHCP server for accessing lease data.
|
||||
dhcpServer DHCP
|
||||
|
||||
// queryLog is the query log for client's DNS requests, responses and
|
||||
// filtering results.
|
||||
queryLog querylog.QueryLog
|
||||
|
||||
// stats is the statistics collector for client's DNS usage data.
|
||||
stats stats.Interface
|
||||
|
||||
// access drops unallowed clients.
|
||||
access *accessManager
|
||||
|
||||
// localDomainSuffix is the suffix used to detect internal hosts. It
|
||||
// must be a valid domain name plus dots on each side.
|
||||
localDomainSuffix string
|
||||
|
||||
ipset ipsetCtx
|
||||
// ipset processes DNS requests using ipset data.
|
||||
ipset ipsetCtx
|
||||
|
||||
// privateNets is the configured set of IP networks considered private.
|
||||
privateNets netutil.SubnetSet
|
||||
|
||||
// addrProc, if not nil, is used to process clients' IP addresses with rDNS,
|
||||
@@ -112,7 +115,10 @@ type Server struct {
|
||||
//
|
||||
// TODO(e.burkov): Remove once the local resolvers logic moved to dnsproxy.
|
||||
localResolvers *proxy.Proxy
|
||||
sysResolvers aghnet.SystemResolvers
|
||||
|
||||
// sysResolvers used to fetch system resolvers to use by default for private
|
||||
// PTR resolving.
|
||||
sysResolvers aghnet.SystemResolvers
|
||||
|
||||
// recDetector is a cache for recursive requests. It is used to detect
|
||||
// and prevent recursive requests only for private upstreams.
|
||||
@@ -128,12 +134,6 @@ type Server struct {
|
||||
// anonymizer masks the client's IP addresses if needed.
|
||||
anonymizer *aghnet.IPMut
|
||||
|
||||
tableHostToIP hostToIPTable
|
||||
tableHostToIPLock sync.Mutex
|
||||
|
||||
tableIPToHost ipToHostTable
|
||||
tableIPToHostLock sync.Mutex
|
||||
|
||||
// clientIDCache is a temporary storage for ClientIDs that were extracted
|
||||
// during the BeforeRequestHandler stage.
|
||||
clientIDCache cache.Cache
|
||||
@@ -142,13 +142,16 @@ type Server struct {
|
||||
// We don't Start() it and so no listen port is required.
|
||||
internalProxy *proxy.Proxy
|
||||
|
||||
// isRunning is true if the DNS server is running.
|
||||
isRunning bool
|
||||
|
||||
// protectionUpdateInProgress is used to make sure that only one goroutine
|
||||
// updating the protection configuration after a pause is running at a time.
|
||||
protectionUpdateInProgress atomic.Bool
|
||||
|
||||
// conf is the current configuration of the server.
|
||||
conf ServerConfig
|
||||
|
||||
// serverLock protects Server.
|
||||
serverLock sync.RWMutex
|
||||
}
|
||||
@@ -164,7 +167,7 @@ type DNSCreateParams struct {
|
||||
DNSFilter *filtering.DNSFilter
|
||||
Stats stats.Interface
|
||||
QueryLog querylog.QueryLog
|
||||
DHCPServer dhcpd.Interface
|
||||
DHCPServer DHCP
|
||||
PrivateNets netutil.SubnetSet
|
||||
Anonymizer *aghnet.IPMut
|
||||
LocalDomain string
|
||||
@@ -200,11 +203,12 @@ func NewServer(p DNSCreateParams) (s *Server, err error) {
|
||||
p.Anonymizer = aghnet.NewIPMut(nil)
|
||||
}
|
||||
s = &Server{
|
||||
dnsFilter: p.DNSFilter,
|
||||
stats: p.Stats,
|
||||
queryLog: p.QueryLog,
|
||||
privateNets: p.PrivateNets,
|
||||
localDomainSuffix: localDomainSuffix,
|
||||
dnsFilter: p.DNSFilter,
|
||||
stats: p.Stats,
|
||||
queryLog: p.QueryLog,
|
||||
privateNets: p.PrivateNets,
|
||||
// TODO(e.burkov): Use some case-insensitive string comparison.
|
||||
localDomainSuffix: strings.ToLower(localDomainSuffix),
|
||||
recDetector: newRecursionDetector(recursionTTL, cachedRecurrentReqNum),
|
||||
clientIDCache: cache.New(cache.Config{
|
||||
EnableLRU: true,
|
||||
@@ -220,11 +224,7 @@ func NewServer(p DNSCreateParams) (s *Server, err error) {
|
||||
return nil, fmt.Errorf("initializing system resolvers: %w", err)
|
||||
}
|
||||
|
||||
if p.DHCPServer != nil {
|
||||
s.dhcpServer = p.DHCPServer
|
||||
s.dhcpServer.SetOnLeaseChanged(s.onDHCPLeaseChanged)
|
||||
s.onDHCPLeaseChanged(dhcpd.LeaseChangedAdded)
|
||||
}
|
||||
s.dhcpServer = p.DHCPServer
|
||||
|
||||
if runtime.GOARCH == "mips" || runtime.GOARCH == "mipsle" {
|
||||
// Use plain DNS on MIPS, encryption is too slow
|
||||
|
||||
@@ -22,7 +22,6 @@ import (
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dhcpd"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering/hashprefix"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering/safesearch"
|
||||
@@ -94,8 +93,13 @@ func createTestServer(
|
||||
|
||||
f.SetEnabled(true)
|
||||
|
||||
dhcp := &testDHCP{
|
||||
OnEnabled: func() (ok bool) { return false },
|
||||
OnHostByIP: func(ip netip.Addr) (host string) { return "" },
|
||||
OnIPByHost: func(host string) (ip netip.Addr) { panic("not implemented") },
|
||||
}
|
||||
s, err = NewServer(DNSCreateParams{
|
||||
DHCPServer: testDHCP,
|
||||
DHCPServer: dhcp,
|
||||
DNSFilter: f,
|
||||
PrivateNets: netutil.SubnetSetFunc(netutil.IsLocallyServed),
|
||||
})
|
||||
@@ -863,8 +867,13 @@ func TestBlockedCustomIP(t *testing.T) {
|
||||
f, err := filtering.New(&filtering.Config{}, filters)
|
||||
require.NoError(t, err)
|
||||
|
||||
dhcp := &testDHCP{
|
||||
OnEnabled: func() (ok bool) { return false },
|
||||
OnHostByIP: func(_ netip.Addr) (host string) { panic("not implemented") },
|
||||
OnIPByHost: func(_ string) (ip netip.Addr) { panic("not implemented") },
|
||||
}
|
||||
s, err := NewServer(DNSCreateParams{
|
||||
DHCPServer: testDHCP,
|
||||
DHCPServer: dhcp,
|
||||
DNSFilter: f,
|
||||
PrivateNets: netutil.SubnetSetFunc(netutil.IsLocallyServed),
|
||||
})
|
||||
@@ -1016,8 +1025,13 @@ func TestRewrite(t *testing.T) {
|
||||
|
||||
f.SetEnabled(true)
|
||||
|
||||
dhcp := &testDHCP{
|
||||
OnEnabled: func() (ok bool) { return false },
|
||||
OnHostByIP: func(ip netip.Addr) (host string) { panic("not implemented") },
|
||||
OnIPByHost: func(host string) (ip netip.Addr) { panic("not implemented") },
|
||||
}
|
||||
s, err := NewServer(DNSCreateParams{
|
||||
DHCPServer: testDHCP,
|
||||
DHCPServer: dhcp,
|
||||
DNSFilter: f,
|
||||
PrivateNets: netutil.SubnetSetFunc(netutil.IsLocallyServed),
|
||||
})
|
||||
@@ -1112,22 +1126,25 @@ func publicKey(priv any) any {
|
||||
}
|
||||
}
|
||||
|
||||
var testDHCP = &dhcpd.MockInterface{
|
||||
OnStart: func() (err error) { panic("not implemented") },
|
||||
OnStop: func() (err error) { panic("not implemented") },
|
||||
OnEnabled: func() (ok bool) { return true },
|
||||
OnLeases: func(flags dhcpd.GetLeasesFlags) (leases []*dhcpd.Lease) {
|
||||
return []*dhcpd.Lease{{
|
||||
IP: netip.MustParseAddr("192.168.12.34"),
|
||||
HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA},
|
||||
Hostname: "myhost",
|
||||
}}
|
||||
},
|
||||
OnSetOnLeaseChanged: func(olct dhcpd.OnLeaseChangedT) {},
|
||||
OnFindMACbyIP: func(ip netip.Addr) (mac net.HardwareAddr) { panic("not implemented") },
|
||||
OnWriteDiskConfig: func(c *dhcpd.ServerConfig) { panic("not implemented") },
|
||||
// testDHCP is a mock implementation of the [DHCP] interface.
|
||||
type testDHCP struct {
|
||||
OnHostByIP func(ip netip.Addr) (host string)
|
||||
OnIPByHost func(host string) (ip netip.Addr)
|
||||
OnEnabled func() (ok bool)
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ DHCP = (*testDHCP)(nil)
|
||||
|
||||
// HostByIP implements the [DHCP] interface for *testDHCP.
|
||||
func (d *testDHCP) HostByIP(ip netip.Addr) (host string) { return d.OnHostByIP(ip) }
|
||||
|
||||
// IPByHost implements the [DHCP] interface for *testDHCP.
|
||||
func (d *testDHCP) IPByHost(host string) (ip netip.Addr) { return d.OnIPByHost(host) }
|
||||
|
||||
// IsClientHost implements the [DHCP] interface for *testDHCP.
|
||||
func (d *testDHCP) Enabled() (ok bool) { return d.OnEnabled() }
|
||||
|
||||
func TestPTRResponseFromDHCPLeases(t *testing.T) {
|
||||
const localDomain = "lan"
|
||||
|
||||
@@ -1135,8 +1152,14 @@ func TestPTRResponseFromDHCPLeases(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
s, err := NewServer(DNSCreateParams{
|
||||
DNSFilter: flt,
|
||||
DHCPServer: testDHCP,
|
||||
DNSFilter: flt,
|
||||
DHCPServer: &testDHCP{
|
||||
OnEnabled: func() (ok bool) { return true },
|
||||
OnIPByHost: func(host string) (ip netip.Addr) { panic("not implemented") },
|
||||
OnHostByIP: func(ip netip.Addr) (host string) {
|
||||
return "myhost"
|
||||
},
|
||||
},
|
||||
PrivateNets: netutil.SubnetSetFunc(netutil.IsLocallyServed),
|
||||
LocalDomain: localDomain,
|
||||
})
|
||||
@@ -1185,6 +1208,12 @@ func TestPTRResponseFromHosts(t *testing.T) {
|
||||
`)},
|
||||
}
|
||||
|
||||
dhcp := &testDHCP{
|
||||
OnEnabled: func() (ok bool) { return false },
|
||||
OnIPByHost: func(host string) (ip netip.Addr) { panic("not implemented") },
|
||||
OnHostByIP: func(ip netip.Addr) (host string) { return "" },
|
||||
}
|
||||
|
||||
var eventsCalledCounter uint32
|
||||
hc, err := aghnet.NewHostsContainer(testFS, &aghtest.FSWatcher{
|
||||
OnEvents: func() (e <-chan struct{}) {
|
||||
@@ -1213,7 +1242,7 @@ func TestPTRResponseFromHosts(t *testing.T) {
|
||||
|
||||
var s *Server
|
||||
s, err = NewServer(DNSCreateParams{
|
||||
DHCPServer: testDHCP,
|
||||
DHCPServer: dhcp,
|
||||
DNSFilter: flt,
|
||||
PrivateNets: netutil.SubnetSetFunc(netutil.IsLocallyServed),
|
||||
})
|
||||
|
||||
@@ -47,7 +47,11 @@ func TestHandleDNSRequest_handleDNSRequest(t *testing.T) {
|
||||
f.SetEnabled(true)
|
||||
|
||||
s, err := NewServer(DNSCreateParams{
|
||||
DHCPServer: testDHCP,
|
||||
DHCPServer: &testDHCP{
|
||||
OnEnabled: func() (ok bool) { return false },
|
||||
OnHostByIP: func(ip netip.Addr) (host string) { panic("not implemented") },
|
||||
OnIPByHost: func(host string) (ip netip.Addr) { panic("not implemented") },
|
||||
},
|
||||
DNSFilter: f,
|
||||
PrivateNets: netutil.SubnetSetFunc(netutil.IsLocallyServed),
|
||||
})
|
||||
@@ -219,7 +223,7 @@ func TestHandleDNSRequest_filterDNSResponse(t *testing.T) {
|
||||
f.SetEnabled(true)
|
||||
|
||||
s, err := NewServer(DNSCreateParams{
|
||||
DHCPServer: testDHCP,
|
||||
DHCPServer: &testDHCP{},
|
||||
DNSFilter: f,
|
||||
PrivateNets: netutil.SubnetSetFunc(netutil.IsLocallyServed),
|
||||
})
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dhcpd"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
|
||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||
@@ -70,20 +69,26 @@ type dnsContext struct {
|
||||
// isLocalClient shows if client's IP address is from locally served
|
||||
// network.
|
||||
isLocalClient bool
|
||||
|
||||
// isDHCPHost is true if the request for a local domain name and the DHCP is
|
||||
// available for this request.
|
||||
isDHCPHost bool
|
||||
}
|
||||
|
||||
// resultCode is the result of a request processing function.
|
||||
type resultCode int
|
||||
|
||||
const (
|
||||
// resultCodeSuccess is returned when a handler performed successfully,
|
||||
// and the next handler must be called.
|
||||
// resultCodeSuccess is returned when a handler performed successfully, and
|
||||
// the next handler must be called.
|
||||
resultCodeSuccess resultCode = iota
|
||||
// resultCodeFinish is returned when a handler performed successfully,
|
||||
// and the processing of the request must be stopped.
|
||||
|
||||
// resultCodeFinish is returned when a handler performed successfully, and
|
||||
// the processing of the request must be stopped.
|
||||
resultCodeFinish
|
||||
// resultCodeError is returned when a handler failed, and the processing
|
||||
// of the request must be stopped.
|
||||
|
||||
// resultCodeError is returned when a handler failed, and the processing of
|
||||
// the request must be stopped.
|
||||
resultCodeError
|
||||
)
|
||||
|
||||
@@ -239,70 +244,6 @@ func (s *Server) processClientIP(addr net.Addr) {
|
||||
s.addrProc.Process(clientIP)
|
||||
}
|
||||
|
||||
func (s *Server) setTableHostToIP(t hostToIPTable) {
|
||||
s.tableHostToIPLock.Lock()
|
||||
defer s.tableHostToIPLock.Unlock()
|
||||
|
||||
s.tableHostToIP = t
|
||||
}
|
||||
|
||||
func (s *Server) setTableIPToHost(t ipToHostTable) {
|
||||
s.tableIPToHostLock.Lock()
|
||||
defer s.tableIPToHostLock.Unlock()
|
||||
|
||||
s.tableIPToHost = t
|
||||
}
|
||||
|
||||
func (s *Server) onDHCPLeaseChanged(flags int) {
|
||||
switch flags {
|
||||
case dhcpd.LeaseChangedAdded,
|
||||
dhcpd.LeaseChangedAddedStatic,
|
||||
dhcpd.LeaseChangedRemovedStatic:
|
||||
// Go on.
|
||||
case dhcpd.LeaseChangedRemovedAll:
|
||||
s.setTableHostToIP(nil)
|
||||
s.setTableIPToHost(nil)
|
||||
|
||||
return
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
ll := s.dhcpServer.Leases(dhcpd.LeasesAll)
|
||||
hostToIP := make(hostToIPTable, len(ll))
|
||||
ipToHost := make(ipToHostTable, len(ll))
|
||||
|
||||
for _, l := range ll {
|
||||
// TODO(a.garipov): Remove this after we're finished with the client
|
||||
// hostname validations in the DHCP server code.
|
||||
err := netutil.ValidateHostname(l.Hostname)
|
||||
if err != nil {
|
||||
log.Debug("dnsforward: skipping invalid hostname %q from dhcp: %s", l.Hostname, err)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
lowhost := strings.ToLower(l.Hostname + "." + s.localDomainSuffix)
|
||||
|
||||
// Assume that we only process IPv4 now.
|
||||
if !l.IP.Is4() {
|
||||
log.Debug("dnsforward: skipping invalid ip from dhcp: bad ipv4 net.IP %v", l.IP)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
leaseIP := l.IP
|
||||
|
||||
ipToHost[leaseIP] = lowhost
|
||||
hostToIP[lowhost] = leaseIP
|
||||
}
|
||||
|
||||
s.setTableHostToIP(hostToIP)
|
||||
s.setTableIPToHost(ipToHost)
|
||||
|
||||
log.Debug("dnsforward: added %d a and ptr entries from dhcp", len(ipToHost))
|
||||
}
|
||||
|
||||
// processDDRQuery responds to Discovery of Designated Resolvers (DDR) SVCB
|
||||
// queries. The response contains different types of encryption supported by
|
||||
// current user configuration.
|
||||
@@ -420,18 +361,6 @@ func (s *Server) processDetermineLocal(dctx *dnsContext) (rc resultCode) {
|
||||
return rc
|
||||
}
|
||||
|
||||
// dhcpHostToIP tries to get an IP leased by DHCP and returns the copy of
|
||||
// address since the data inside the internal table may be changed while request
|
||||
// processing. It's safe for concurrent use.
|
||||
func (s *Server) dhcpHostToIP(host string) (ip netip.Addr, ok bool) {
|
||||
s.tableHostToIPLock.Lock()
|
||||
defer s.tableHostToIPLock.Unlock()
|
||||
|
||||
ip, ok = s.tableHostToIP[host]
|
||||
|
||||
return ip, ok
|
||||
}
|
||||
|
||||
// processDHCPHosts respond to A requests if the target hostname is known to
|
||||
// the server. It responds with a mapped IP address if the DNS64 is enabled and
|
||||
// the request is for AAAA.
|
||||
@@ -443,30 +372,31 @@ func (s *Server) processDHCPHosts(dctx *dnsContext) (rc resultCode) {
|
||||
|
||||
pctx := dctx.proxyCtx
|
||||
req := pctx.Req
|
||||
q := req.Question[0]
|
||||
reqHost, ok := s.isDHCPClientHostQ(q)
|
||||
if !ok {
|
||||
|
||||
q := &req.Question[0]
|
||||
dhcpHost := s.dhcpHostFromRequest(q)
|
||||
if dctx.isDHCPHost = dhcpHost != ""; !dctx.isDHCPHost {
|
||||
return resultCodeSuccess
|
||||
}
|
||||
|
||||
if !dctx.isLocalClient {
|
||||
log.Debug("dnsforward: %q requests for dhcp host %q", pctx.Addr, reqHost)
|
||||
log.Debug("dnsforward: %q requests for dhcp host %q", pctx.Addr, dhcpHost)
|
||||
pctx.Res = s.genNXDomain(req)
|
||||
|
||||
// Do not even put into query log.
|
||||
return resultCodeFinish
|
||||
}
|
||||
|
||||
ip, ok := s.dhcpHostToIP(reqHost)
|
||||
if !ok {
|
||||
ip := s.dhcpServer.IPByHost(dhcpHost)
|
||||
if ip == (netip.Addr{}) {
|
||||
// Go on and process them with filters, including dnsrewrite ones, and
|
||||
// possibly route them to a domain-specific upstream.
|
||||
log.Debug("dnsforward: no dhcp record for %q", reqHost)
|
||||
log.Debug("dnsforward: no dhcp record for %q", dhcpHost)
|
||||
|
||||
return resultCodeSuccess
|
||||
}
|
||||
|
||||
log.Debug("dnsforward: dhcp record for %q is %s", reqHost, ip)
|
||||
log.Debug("dnsforward: dhcp record for %q is %s", dhcpHost, ip)
|
||||
|
||||
resp := s.makeResponse(req)
|
||||
switch q.Qtype {
|
||||
@@ -638,17 +568,6 @@ func (s *Server) processRestrictLocal(dctx *dnsContext) (rc resultCode) {
|
||||
return resultCodeSuccess
|
||||
}
|
||||
|
||||
// ipToDHCPHost tries to get a hostname leased by DHCP. It's safe for
|
||||
// concurrent use.
|
||||
func (s *Server) ipToDHCPHost(ip netip.Addr) (host string, ok bool) {
|
||||
s.tableIPToHostLock.Lock()
|
||||
defer s.tableIPToHostLock.Unlock()
|
||||
|
||||
host, ok = s.tableIPToHost[ip]
|
||||
|
||||
return host, ok
|
||||
}
|
||||
|
||||
// processDHCPAddrs responds to PTR requests if the target IP is leased by the
|
||||
// DHCP server.
|
||||
func (s *Server) processDHCPAddrs(dctx *dnsContext) (rc resultCode) {
|
||||
@@ -673,12 +592,12 @@ func (s *Server) processDHCPAddrs(dctx *dnsContext) (rc resultCode) {
|
||||
return resultCodeSuccess
|
||||
}
|
||||
|
||||
host, ok := s.ipToDHCPHost(ipAddr)
|
||||
if !ok {
|
||||
host := s.dhcpServer.HostByIP(ipAddr)
|
||||
if host == "" {
|
||||
return resultCodeSuccess
|
||||
}
|
||||
|
||||
log.Debug("dnsforward: dhcp reverse record for %s is %q", ip, host)
|
||||
log.Debug("dnsforward: dhcp client %s is %q", ip, host)
|
||||
|
||||
req := pctx.Req
|
||||
resp := s.makeResponse(req)
|
||||
@@ -686,10 +605,12 @@ func (s *Server) processDHCPAddrs(dctx *dnsContext) (rc resultCode) {
|
||||
Hdr: dns.RR_Header{
|
||||
Name: req.Question[0].Name,
|
||||
Rrtype: dns.TypePTR,
|
||||
Ttl: s.conf.BlockedResponseTTL,
|
||||
Class: dns.ClassINET,
|
||||
// TODO(e.burkov): Use [dhcpsvc.Lease.Expiry]. See
|
||||
// https://github.com/AdguardTeam/AdGuardHome/issues/3932.
|
||||
Ttl: s.conf.BlockedResponseTTL,
|
||||
Class: dns.ClassINET,
|
||||
},
|
||||
Ptr: dns.Fqdn(host),
|
||||
Ptr: dns.Fqdn(strings.Join([]string{host, s.localDomainSuffix}, ".")),
|
||||
}
|
||||
resp.Answer = append(resp.Answer, ptr)
|
||||
pctx.Res = resp
|
||||
@@ -788,17 +709,18 @@ func (s *Server) processUpstream(dctx *dnsContext) (rc resultCode) {
|
||||
|
||||
pctx := dctx.proxyCtx
|
||||
req := pctx.Req
|
||||
q := req.Question[0]
|
||||
|
||||
if pctx.Res != nil {
|
||||
// The response has already been set.
|
||||
return resultCodeSuccess
|
||||
} else if reqHost, ok := s.isDHCPClientHostQ(q); ok {
|
||||
} else if dctx.isDHCPHost {
|
||||
// A DHCP client hostname query that hasn't been handled or filtered.
|
||||
// Respond with an NXDOMAIN.
|
||||
//
|
||||
// TODO(a.garipov): Route such queries to a custom upstream for the
|
||||
// local domain name if there is one.
|
||||
log.Debug("dnsforward: dhcp client hostname %q was not filtered", reqHost)
|
||||
name := req.Question[0].Name
|
||||
log.Debug("dnsforward: dhcp client hostname %q was not filtered", name[:len(name)-1])
|
||||
pctx.Res = s.genNXDomain(req)
|
||||
|
||||
return resultCodeFinish
|
||||
@@ -885,26 +807,26 @@ func (s *Server) setRespAD(pctx *proxy.DNSContext, reqWantsDNSSEC bool) {
|
||||
}
|
||||
}
|
||||
|
||||
// isDHCPClientHostQ returns true if q is from a request for a DHCP client
|
||||
// hostname. If ok is true, reqHost contains the requested hostname.
|
||||
func (s *Server) isDHCPClientHostQ(q dns.Question) (reqHost string, ok bool) {
|
||||
// dhcpHostFromRequest returns a hostname from question, if the request is for a
|
||||
// DHCP client's hostname when DHCP is enabled, and an empty string otherwise.
|
||||
func (s *Server) dhcpHostFromRequest(q *dns.Question) (reqHost string) {
|
||||
if !s.dhcpServer.Enabled() {
|
||||
return "", false
|
||||
return ""
|
||||
}
|
||||
|
||||
// Include AAAA here, because despite the fact that we don't support it yet,
|
||||
// the expected behavior here is to respond with an empty answer and not
|
||||
// NXDOMAIN.
|
||||
if qt := q.Qtype; qt != dns.TypeA && qt != dns.TypeAAAA {
|
||||
return "", false
|
||||
return ""
|
||||
}
|
||||
|
||||
reqHost = strings.ToLower(q.Name[:len(q.Name)-1])
|
||||
if strings.HasSuffix(reqHost, s.localDomainSuffix) {
|
||||
return reqHost, true
|
||||
if !netutil.IsImmediateSubdomain(reqHost, s.localDomainSuffix) {
|
||||
return ""
|
||||
}
|
||||
|
||||
return "", false
|
||||
return reqHost[:len(reqHost)-len(s.localDomainSuffix)-1]
|
||||
}
|
||||
|
||||
// setCustomUpstream sets custom upstream settings in pctx, if necessary.
|
||||
|
||||
@@ -416,47 +416,58 @@ func TestServer_ProcessDetermineLocal(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestServer_ProcessDHCPHosts_localRestriction(t *testing.T) {
|
||||
const (
|
||||
localDomainSuffix = "lan"
|
||||
dhcpClient = "example"
|
||||
|
||||
knownHost = dhcpClient + "." + localDomainSuffix
|
||||
unknownHost = "wronghost." + localDomainSuffix
|
||||
)
|
||||
|
||||
knownIP := netip.MustParseAddr("1.2.3.4")
|
||||
dhcp := &testDHCP{
|
||||
OnEnabled: func() (_ bool) { return true },
|
||||
OnIPByHost: func(host string) (ip netip.Addr) {
|
||||
if host == dhcpClient {
|
||||
ip = knownIP
|
||||
}
|
||||
|
||||
return ip
|
||||
},
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
wantIP netip.Addr
|
||||
name string
|
||||
host string
|
||||
wantRes resultCode
|
||||
isLocalCli bool
|
||||
}{{
|
||||
wantIP: knownIP,
|
||||
name: "local_client_success",
|
||||
host: "example.lan",
|
||||
wantRes: resultCodeSuccess,
|
||||
host: knownHost,
|
||||
isLocalCli: true,
|
||||
}, {
|
||||
wantIP: netip.Addr{},
|
||||
name: "local_client_unknown_host",
|
||||
host: "wronghost.lan",
|
||||
wantRes: resultCodeSuccess,
|
||||
host: unknownHost,
|
||||
isLocalCli: true,
|
||||
}, {
|
||||
wantIP: netip.Addr{},
|
||||
name: "external_client_known_host",
|
||||
host: "example.lan",
|
||||
wantRes: resultCodeFinish,
|
||||
host: knownHost,
|
||||
isLocalCli: false,
|
||||
}, {
|
||||
wantIP: netip.Addr{},
|
||||
name: "external_client_unknown_host",
|
||||
host: "wronghost.lan",
|
||||
wantRes: resultCodeFinish,
|
||||
host: unknownHost,
|
||||
isLocalCli: false,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
s := &Server{
|
||||
dhcpServer: testDHCP,
|
||||
localDomainSuffix: defaultLocalDomainSuffix,
|
||||
tableHostToIP: hostToIPTable{
|
||||
"example." + defaultLocalDomainSuffix: knownIP,
|
||||
},
|
||||
dhcpServer: dhcp,
|
||||
localDomainSuffix: localDomainSuffix,
|
||||
}
|
||||
|
||||
req := &dns.Msg{
|
||||
@@ -478,43 +489,52 @@ func TestServer_ProcessDHCPHosts_localRestriction(t *testing.T) {
|
||||
}
|
||||
|
||||
res := s.processDHCPHosts(dctx)
|
||||
require.Equal(t, tc.wantRes, res)
|
||||
|
||||
pctx := dctx.proxyCtx
|
||||
if tc.wantRes == resultCodeFinish {
|
||||
if !tc.isLocalCli {
|
||||
require.Equal(t, resultCodeFinish, res)
|
||||
require.NotNil(t, pctx.Res)
|
||||
|
||||
assert.Equal(t, dns.RcodeNameError, pctx.Res.Rcode)
|
||||
assert.Len(t, pctx.Res.Answer, 0)
|
||||
assert.Empty(t, pctx.Res.Answer)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
require.Equal(t, resultCodeSuccess, res)
|
||||
|
||||
if tc.wantIP == (netip.Addr{}) {
|
||||
assert.Nil(t, pctx.Res)
|
||||
} else {
|
||||
require.NotNil(t, pctx.Res)
|
||||
|
||||
ans := pctx.Res.Answer
|
||||
require.Len(t, ans, 1)
|
||||
|
||||
a := testutil.RequireTypeAssert[*dns.A](t, ans[0])
|
||||
|
||||
ip, err := netutil.IPToAddr(a.A, netutil.AddrFamilyIPv4)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, tc.wantIP, ip)
|
||||
return
|
||||
}
|
||||
|
||||
require.NotNil(t, pctx.Res)
|
||||
|
||||
ans := pctx.Res.Answer
|
||||
require.Len(t, ans, 1)
|
||||
|
||||
a := testutil.RequireTypeAssert[*dns.A](t, ans[0])
|
||||
|
||||
ip, err := netutil.IPToAddr(a.A, netutil.AddrFamilyIPv4)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, tc.wantIP, ip)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_ProcessDHCPHosts(t *testing.T) {
|
||||
const (
|
||||
examplecom = "example.com"
|
||||
examplelan = "example." + defaultLocalDomainSuffix
|
||||
localTLD = "lan"
|
||||
|
||||
knownClient = "example"
|
||||
externalHost = knownClient + ".com"
|
||||
clientHost = knownClient + "." + localTLD
|
||||
)
|
||||
|
||||
knownIP := netip.MustParseAddr("1.2.3.4")
|
||||
|
||||
testCases := []struct {
|
||||
wantIP netip.Addr
|
||||
name string
|
||||
@@ -524,55 +544,64 @@ func TestServer_ProcessDHCPHosts(t *testing.T) {
|
||||
qtyp uint16
|
||||
}{{
|
||||
wantIP: netip.Addr{},
|
||||
name: "success_external",
|
||||
host: examplecom,
|
||||
suffix: defaultLocalDomainSuffix,
|
||||
name: "external",
|
||||
host: externalHost,
|
||||
suffix: localTLD,
|
||||
wantRes: resultCodeSuccess,
|
||||
qtyp: dns.TypeA,
|
||||
}, {
|
||||
wantIP: netip.Addr{},
|
||||
name: "success_external_non_a",
|
||||
host: examplecom,
|
||||
suffix: defaultLocalDomainSuffix,
|
||||
name: "external_non_a",
|
||||
host: externalHost,
|
||||
suffix: localTLD,
|
||||
wantRes: resultCodeSuccess,
|
||||
qtyp: dns.TypeCNAME,
|
||||
}, {
|
||||
wantIP: knownIP,
|
||||
name: "success_internal",
|
||||
host: examplelan,
|
||||
suffix: defaultLocalDomainSuffix,
|
||||
name: "internal",
|
||||
host: clientHost,
|
||||
suffix: localTLD,
|
||||
wantRes: resultCodeSuccess,
|
||||
qtyp: dns.TypeA,
|
||||
}, {
|
||||
wantIP: netip.Addr{},
|
||||
name: "success_internal_unknown",
|
||||
name: "internal_unknown",
|
||||
host: "example-new.lan",
|
||||
suffix: defaultLocalDomainSuffix,
|
||||
suffix: localTLD,
|
||||
wantRes: resultCodeSuccess,
|
||||
qtyp: dns.TypeA,
|
||||
}, {
|
||||
wantIP: netip.Addr{},
|
||||
name: "success_internal_aaaa",
|
||||
host: examplelan,
|
||||
suffix: defaultLocalDomainSuffix,
|
||||
name: "internal_aaaa",
|
||||
host: clientHost,
|
||||
suffix: localTLD,
|
||||
wantRes: resultCodeSuccess,
|
||||
qtyp: dns.TypeAAAA,
|
||||
}, {
|
||||
wantIP: knownIP,
|
||||
name: "success_custom_suffix",
|
||||
host: "example.custom",
|
||||
name: "custom_suffix",
|
||||
host: knownClient + ".custom",
|
||||
suffix: "custom",
|
||||
wantRes: resultCodeSuccess,
|
||||
qtyp: dns.TypeA,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
testDHCP := &testDHCP{
|
||||
OnEnabled: func() (_ bool) { return true },
|
||||
OnIPByHost: func(host string) (ip netip.Addr) {
|
||||
if host == knownClient {
|
||||
ip = knownIP
|
||||
}
|
||||
|
||||
return ip
|
||||
},
|
||||
OnHostByIP: func(ip netip.Addr) (host string) { panic("not implemented") },
|
||||
}
|
||||
|
||||
s := &Server{
|
||||
dhcpServer: testDHCP,
|
||||
localDomainSuffix: tc.suffix,
|
||||
tableHostToIP: hostToIPTable{
|
||||
"example." + tc.suffix: knownIP,
|
||||
},
|
||||
}
|
||||
|
||||
req := &dns.Msg{
|
||||
@@ -597,13 +626,6 @@ func TestServer_ProcessDHCPHosts(t *testing.T) {
|
||||
res := s.processDHCPHosts(dctx)
|
||||
pctx := dctx.proxyCtx
|
||||
assert.Equal(t, tc.wantRes, res)
|
||||
if tc.wantRes == resultCodeFinish {
|
||||
require.NotNil(t, pctx.Res)
|
||||
assert.Equal(t, dns.RcodeNameError, pctx.Res.Rcode)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, dctx.err)
|
||||
|
||||
if tc.qtyp == dns.TypeAAAA {
|
||||
|
||||
Reference in New Issue
Block a user