all: imp client resolving

This commit is contained in:
Ainar Garipov
2023-07-13 18:18:49 +03:00
parent f22d893845
commit 9f93a21bf6
9 changed files with 221 additions and 164 deletions

View File

@@ -25,6 +25,8 @@ NOTE: Add new changes BELOW THIS COMMENT.
### Fixed ### Fixed
- Occasional client information lookup failures leading to DNS resolving getting
stuck ([#6006]).
- `bufio.Scanner: token too long` errors when trying to add filtering-rule lists - `bufio.Scanner: token too long` errors when trying to add filtering-rule lists
with lines over 1024 bytes long ([#6003]). with lines over 1024 bytes long ([#6003]).
@@ -34,6 +36,7 @@ NOTE: Add new changes BELOW THIS COMMENT.
the `Dockerfile`. the `Dockerfile`.
[#6003]: https://github.com/AdguardTeam/AdGuardHome/issues/6003 [#6003]: https://github.com/AdguardTeam/AdGuardHome/issues/6003
[#6006]: https://github.com/AdguardTeam/AdGuardHome/issues/6006
<!-- <!--
NOTE: Add new changes ABOVE THIS COMMENT. NOTE: Add new changes ABOVE THIS COMMENT.

View File

@@ -270,7 +270,10 @@ type ServerConfig struct {
UDPListenAddrs []*net.UDPAddr // UDP listen address UDPListenAddrs []*net.UDPAddr // UDP listen address
TCPListenAddrs []*net.TCPAddr // TCP listen address TCPListenAddrs []*net.TCPAddr // TCP listen address
UpstreamConfig *proxy.UpstreamConfig // Upstream DNS servers config UpstreamConfig *proxy.UpstreamConfig // Upstream DNS servers config
OnDNSRequest func(d *proxy.DNSContext)
// ClientIPs, if not nil, is used to send clients' IP addresses to other
// parts of AdGuard Home that may use it for resolving rDNS, WHOIS, etc.
ClientIPs chan netip.Addr
FilteringConfig FilteringConfig
TLSConfig TLSConfig

View File

@@ -99,6 +99,10 @@ type Server struct {
// must be a valid domain name plus dots on each side. // must be a valid domain name plus dots on each side.
localDomainSuffix string localDomainSuffix string
// ClientIPs, if not nil, is used to send clients' IP addresses to other
// parts of AdGuard Home that may use it for resolving rDNS, WHOIS, etc.
clientIPs chan<- netip.Addr
ipset ipsetCtx ipset ipsetCtx
privateNets netutil.SubnetSet privateNets netutil.SubnetSet
localResolvers *proxy.Proxy localResolvers *proxy.Proxy
@@ -318,7 +322,8 @@ func (s *Server) Exchange(ip netip.Addr) (host string, err error) {
Qclass: dns.ClassINET, Qclass: dns.ClassINET,
}}, }},
} }
ctx := &proxy.DNSContext{
dctx := &proxy.DNSContext{
Proto: "udp", Proto: "udp",
Req: req, Req: req,
StartTime: time.Now(), StartTime: time.Now(),
@@ -336,11 +341,11 @@ func (s *Server) Exchange(ip netip.Addr) (host string, err error) {
resolver = s.internalProxy resolver = s.internalProxy
} }
if err = resolver.Resolve(ctx); err != nil { if err = resolver.Resolve(dctx); err != nil {
return "", err return "", err
} }
return hostFromPTR(ctx.Res) return hostFromPTR(dctx.Res)
} }
// hostFromPTR returns domain name from the PTR response or error. // hostFromPTR returns domain name from the PTR response or error.
@@ -555,6 +560,8 @@ func (s *Server) Prepare(conf *ServerConfig) (err error) {
s.recDetector.clear() s.recDetector.clear()
s.clientIPs = s.conf.ClientIPs
return nil return nil
} }
@@ -696,6 +703,9 @@ func (s *Server) Reconfigure(conf *ServerConfig) error {
// TODO(a.garipov): This whole piece of API is weird and needs to be remade. // TODO(a.garipov): This whole piece of API is weird and needs to be remade.
if conf == nil { if conf == nil {
conf = &s.conf conf = &s.conf
} else if s.clientIPs != nil {
close(s.clientIPs)
s.clientIPs = nil
} }
err = s.Prepare(conf) err = s.Prepare(conf)

View File

@@ -30,6 +30,7 @@ type dnsContext struct {
setts *filtering.Settings setts *filtering.Settings
result *filtering.Result result *filtering.Result
// origResp is the response received from upstream. It is set when the // origResp is the response received from upstream. It is set when the
// response is modified by filters. // response is modified by filters.
origResp *dns.Msg origResp *dns.Msg
@@ -48,13 +49,13 @@ type dnsContext struct {
// clientID is the ClientID from DoH, DoQ, or DoT, if provided. // clientID is the ClientID from DoH, DoQ, or DoT, if provided.
clientID string clientID string
// startTime is the time at which the processing of the request has started.
startTime time.Time
// origQuestion is the question received from the client. It is set // origQuestion is the question received from the client. It is set
// when the request is modified by rewrites. // when the request is modified by rewrites.
origQuestion dns.Question origQuestion dns.Question
// startTime is the time at which the processing of the request has started.
startTime time.Time
// protectionEnabled shows if the filtering is enabled, and if the // protectionEnabled shows if the filtering is enabled, and if the
// server's DNS filter is ready. // server's DNS filter is ready.
protectionEnabled bool protectionEnabled bool
@@ -177,9 +178,7 @@ func (s *Server) processInitial(dctx *dnsContext) (rc resultCode) {
return resultCodeFinish return resultCodeFinish
} }
if s.conf.OnDNSRequest != nil { s.processClientIP(pctx.Addr)
s.conf.OnDNSRequest(pctx)
}
// Disable Mozilla DoH. // Disable Mozilla DoH.
// //
@@ -218,6 +217,28 @@ func (s *Server) processInitial(dctx *dnsContext) (rc resultCode) {
return resultCodeSuccess return resultCodeSuccess
} }
// processClientIP sends the client IP address to s.clientIPs, if needed.
func (s *Server) processClientIP(addr net.Addr) {
clientIP := netutil.NetAddrToAddrPort(addr).Addr()
if clientIP == (netip.Addr{}) {
log.Info("dnsforward: warning: bad client addr %q", addr)
return
}
// Do not assign s.clientIPs to a local variable to then use, since this
// lock also serializes the closure of s.clientIPs.
s.serverLock.RLock()
defer s.serverLock.RUnlock()
select {
case s.clientIPs <- clientIP:
// Go on.
default:
log.Debug("dnsforward: client ip channel is nil or full; len: %d", len(s.clientIPs))
}
}
func (s *Server) setTableHostToIP(t hostToIPTable) { func (s *Server) setTableHostToIP(t hostToIPTable) {
s.tableHostToIPLock.Lock() s.tableHostToIPLock.Lock()
defer s.tableHostToIPLock.Unlock() defer s.tableHostToIPLock.Unlock()

146
internal/home/clientaddr.go Normal file
View File

@@ -0,0 +1,146 @@
package home
import (
"context"
"net/netip"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/rdns"
"github.com/AdguardTeam/AdGuardHome/internal/whois"
"github.com/AdguardTeam/golibs/log"
)
// TODO(a.garipov): It is currently hard to add tests for this structure due to
// strong coupling between it and Context.dnsServer with Context.clients.
// Resolve this coupling and add proper testing.
// clientAddrProcessor processes incoming client addresses with rDNS and WHOIS,
// if configured.
type clientAddrProcessor struct {
rdns rdns.Interface
whois whois.Interface
}
const (
// defaultQueueSize is the size of queue of IPs for rDNS and WHOIS
// processing.
defaultQueueSize = 255
// defaultCacheSize is the maximum size of the cache for rDNS and WHOIS
// processing. It must be greater than zero.
defaultCacheSize = 10_000
// defaultIPTTL is the Time to Live duration for IP addresses cached by
// rDNS and WHOIS.
defaultIPTTL = 1 * time.Hour
)
// newClientAddrProcessor returns a new client address processor. c must not be
// nil.
func newClientAddrProcessor(c *clientSourcesConfig) (p *clientAddrProcessor) {
p = &clientAddrProcessor{}
if c.RDNS {
p.rdns = rdns.New(&rdns.Config{
Exchanger: Context.dnsServer,
CacheSize: defaultCacheSize,
CacheTTL: defaultIPTTL,
})
} else {
p.rdns = rdns.Empty{}
}
if c.WHOIS {
// TODO(s.chzhen): Consider making configurable.
const (
// defaultTimeout is the timeout for WHOIS requests.
defaultTimeout = 5 * time.Second
// defaultMaxConnReadSize is an upper limit in bytes for reading from a
// net.Conn.
defaultMaxConnReadSize = 64 * 1024
// defaultMaxRedirects is the maximum redirects count.
defaultMaxRedirects = 5
// defaultMaxInfoLen is the maximum length of whois.Info fields.
defaultMaxInfoLen = 250
)
p.whois = whois.New(&whois.Config{
DialContext: customDialContext,
ServerAddr: whois.DefaultServer,
Port: whois.DefaultPort,
Timeout: defaultTimeout,
CacheSize: defaultCacheSize,
MaxConnReadSize: defaultMaxConnReadSize,
MaxRedirects: defaultMaxRedirects,
MaxInfoLen: defaultMaxInfoLen,
CacheTTL: defaultIPTTL,
})
} else {
p.whois = whois.Empty{}
}
return p
}
// process processes the incoming client IP-address information. It is intended
// to be used as a goroutine.
func (p *clientAddrProcessor) process(clientIPs <-chan netip.Addr) {
defer log.OnPanic("clientAddrProcessor.process")
log.Info("home: processing client addresses")
for ip := range clientIPs {
p.processRDNS(ip)
p.processWHOIS(ip)
}
log.Info("home: finished processing client addresses")
}
// processRDNS resolves the clients' IP addresses using reverse DNS.
func (p *clientAddrProcessor) processRDNS(ip netip.Addr) {
start := time.Now()
log.Debug("home: processing client %s with rdns", ip)
defer func() {
log.Debug("home: finished processing client %s with rdns in %s", ip, time.Since(start))
}()
ok := Context.dnsServer.ShouldResolveClient(ip)
if !ok {
return
}
host, changed := p.rdns.Process(ip)
if host == "" || !changed {
return
}
ok = Context.clients.AddHost(ip, host, ClientSourceRDNS)
if ok {
return
}
log.Debug("dns: setting rdns info for client %q: already set with higher priority source", ip)
}
// processWHOIS looks up the information aobut clients' IP addresses in the
// WHOIS databases.
func (p *clientAddrProcessor) processWHOIS(ip netip.Addr) {
start := time.Now()
log.Debug("home: processing client %s with whois", ip)
defer func() {
log.Debug("home: finished processing client %s with whois in %s", ip, time.Since(start))
}()
// TODO(s.chzhen): Move the timeout logic from WHOIS configuration to the
// context.
info, changed := p.whois.Process(context.Background(), ip)
if info == nil || !changed {
return
}
Context.clients.setWHOISInfo(ip, info)
}

View File

@@ -141,7 +141,7 @@ func (clients *clientsContainer) handleHostsUpdates() {
} }
} }
// webHandlersRegistered prevents a [clientsContainer] from regisering its web // webHandlersRegistered prevents a [clientsContainer] from registering its web
// handlers more than once. // handlers more than once.
// //
// TODO(a.garipov): Refactor HTTP handler registration logic. // TODO(a.garipov): Refactor HTTP handler registration logic.

View File

@@ -17,10 +17,7 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward" "github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
"github.com/AdguardTeam/AdGuardHome/internal/filtering" "github.com/AdguardTeam/AdGuardHome/internal/filtering"
"github.com/AdguardTeam/AdGuardHome/internal/querylog" "github.com/AdguardTeam/AdGuardHome/internal/querylog"
"github.com/AdguardTeam/AdGuardHome/internal/rdns"
"github.com/AdguardTeam/AdGuardHome/internal/stats" "github.com/AdguardTeam/AdGuardHome/internal/stats"
"github.com/AdguardTeam/AdGuardHome/internal/whois"
"github.com/AdguardTeam/dnsproxy/proxy"
"github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/golibs/netutil"
@@ -154,134 +151,32 @@ func initDNSServer(
Context.clients.dnsServer = Context.dnsServer Context.clients.dnsServer = Context.dnsServer
dnsConf, err := generateServerConfig(tlsConf, httpReg) dnsConf, err := newServerConfig(tlsConf, httpReg)
if err != nil { if err != nil {
closeDNSServer() closeDNSServer()
return fmt.Errorf("generateServerConfig: %w", err) return fmt.Errorf("newServerConfig: %w", err)
} }
err = Context.dnsServer.Prepare(&dnsConf) err = Context.dnsServer.Prepare(dnsConf)
if err != nil { if err != nil {
closeDNSServer() closeDNSServer()
return fmt.Errorf("dnsServer.Prepare: %w", err) return fmt.Errorf("dnsServer.Prepare: %w", err)
} }
initRDNS() clientIPs := dnsConf.ClientIPs
initWHOIS() addrProc := newClientAddrProcessor(config.Clients.Sources)
go addrProc.process(clientIPs)
const topClientsNumber = 100
for _, ip := range Context.stats.TopClientsIP(topClientsNumber) {
clientIPs <- ip
}
return nil return nil
} }
const (
// defaultQueueSize is the size of queue of IPs for rDNS and WHOIS
// processing.
defaultQueueSize = 255
// defaultCacheSize is the maximum size of the cache for rDNS and WHOIS
// processing. It must be greater than zero.
defaultCacheSize = 10_000
// defaultIPTTL is the Time to Live duration for IP addresses cached by
// rDNS and WHOIS.
defaultIPTTL = 1 * time.Hour
)
// initRDNS initializes the rDNS.
func initRDNS() {
Context.rdnsCh = make(chan netip.Addr, defaultQueueSize)
// TODO(s.chzhen): Add ability to disable it on dns server configuration
// update in [dnsforward] package.
r := rdns.New(&rdns.Config{
Exchanger: Context.dnsServer,
CacheSize: defaultCacheSize,
CacheTTL: defaultIPTTL,
})
go processRDNS(r)
}
// processRDNS processes reverse DNS lookup queries. It is intended to be used
// as a goroutine.
func processRDNS(r rdns.Interface) {
defer log.OnPanic("rdns")
for ip := range Context.rdnsCh {
ok := Context.dnsServer.ShouldResolveClient(ip)
if !ok {
continue
}
host, changed := r.Process(ip)
if host == "" || !changed {
continue
}
ok = Context.clients.AddHost(ip, host, ClientSourceRDNS)
if ok {
continue
}
log.Debug(
"dns: can't set rdns info for client %q: already set with higher priority source",
ip,
)
}
}
// initWHOIS initializes the WHOIS.
//
// TODO(s.chzhen): Consider making configurable.
func initWHOIS() {
const (
// defaultTimeout is the timeout for WHOIS requests.
defaultTimeout = 5 * time.Second
// defaultMaxConnReadSize is an upper limit in bytes for reading from
// net.Conn.
defaultMaxConnReadSize = 64 * 1024
// defaultMaxRedirects is the maximum redirects count.
defaultMaxRedirects = 5
// defaultMaxInfoLen is the maximum length of whois.Info fields.
defaultMaxInfoLen = 250
)
Context.whoisCh = make(chan netip.Addr, defaultQueueSize)
var w whois.Interface
if config.Clients.Sources.WHOIS {
w = whois.New(&whois.Config{
DialContext: customDialContext,
ServerAddr: whois.DefaultServer,
Port: whois.DefaultPort,
Timeout: defaultTimeout,
CacheSize: defaultCacheSize,
MaxConnReadSize: defaultMaxConnReadSize,
MaxRedirects: defaultMaxRedirects,
MaxInfoLen: defaultMaxInfoLen,
CacheTTL: defaultIPTTL,
})
} else {
w = whois.Empty{}
}
go func() {
defer log.OnPanic("whois")
for ip := range Context.whoisCh {
info, changed := w.Process(context.Background(), ip)
if info != nil && changed {
Context.clients.setWHOISInfo(ip, info)
}
}
}()
}
// parseSubnetSet parses a slice of subnets. If the slice is empty, it returns // parseSubnetSet parses a slice of subnets. If the slice is empty, it returns
// a subnet set that matches all locally served networks, see // a subnet set that matches all locally served networks, see
// [netutil.IsLocallyServed]. // [netutil.IsLocallyServed].
@@ -312,17 +207,6 @@ func isRunning() bool {
return Context.dnsServer != nil && Context.dnsServer.IsRunning() return Context.dnsServer != nil && Context.dnsServer.IsRunning()
} }
func onDNSRequest(pctx *proxy.DNSContext) {
ip := netutil.NetAddrToAddrPort(pctx.Addr).Addr()
if ip == (netip.Addr{}) {
// This would be quite weird if we get here.
return
}
Context.rdnsCh <- ip
Context.whoisCh <- ip
}
func ipsToTCPAddrs(ips []netip.Addr, port int) (tcpAddrs []*net.TCPAddr) { func ipsToTCPAddrs(ips []netip.Addr, port int) (tcpAddrs []*net.TCPAddr) {
if ips == nil { if ips == nil {
return nil return nil
@@ -349,19 +233,20 @@ func ipsToUDPAddrs(ips []netip.Addr, port int) (udpAddrs []*net.UDPAddr) {
return udpAddrs return udpAddrs
} }
func generateServerConfig( func newServerConfig(
tlsConf *tlsConfigSettings, tlsConf *tlsConfigSettings,
httpReg aghhttp.RegisterFunc, httpReg aghhttp.RegisterFunc,
) (newConf dnsforward.ServerConfig, err error) { ) (newConf *dnsforward.ServerConfig, err error) {
dnsConf := config.DNS dnsConf := config.DNS
hosts := aghalg.CoalesceSlice(dnsConf.BindHosts, []netip.Addr{netutil.IPv4Localhost()}) hosts := aghalg.CoalesceSlice(dnsConf.BindHosts, []netip.Addr{netutil.IPv4Localhost()})
newConf = dnsforward.ServerConfig{ clientIPs := make(chan netip.Addr, defaultQueueSize)
newConf = &dnsforward.ServerConfig{
UDPListenAddrs: ipsToUDPAddrs(hosts, dnsConf.Port), UDPListenAddrs: ipsToUDPAddrs(hosts, dnsConf.Port),
TCPListenAddrs: ipsToTCPAddrs(hosts, dnsConf.Port), TCPListenAddrs: ipsToTCPAddrs(hosts, dnsConf.Port),
FilteringConfig: dnsConf.FilteringConfig, FilteringConfig: dnsConf.FilteringConfig,
ConfigModified: onConfigModified, ConfigModified: onConfigModified,
HTTPRegister: httpReg, HTTPRegister: httpReg,
OnDNSRequest: onDNSRequest, ClientIPs: clientIPs,
UseDNS64: config.DNS.UseDNS64, UseDNS64: config.DNS.UseDNS64,
DNS64Prefixes: config.DNS.DNS64Prefixes, DNS64Prefixes: config.DNS.DNS64Prefixes,
} }
@@ -385,9 +270,9 @@ func generateServerConfig(
if tlsConf.PortDNSCrypt != 0 { if tlsConf.PortDNSCrypt != 0 {
newConf.DNSCryptConfig, err = newDNSCrypt(hosts, *tlsConf) newConf.DNSCryptConfig, err = newDNSCrypt(hosts, *tlsConf)
if err != nil { if err != nil {
// Don't wrap the error, because it's already // Don't wrap the error, because it's already wrapped by
// wrapped by newDNSCrypt. // newDNSCrypt.
return dnsforward.ServerConfig{}, err return nil, err
} }
} }
} }
@@ -556,31 +441,26 @@ func startDNSServer() error {
Context.stats.Start() Context.stats.Start()
Context.queryLog.Start() Context.queryLog.Start()
const topClientsNumber = 100 // the number of clients to get
for _, ip := range Context.stats.TopClientsIP(topClientsNumber) {
Context.rdnsCh <- ip
Context.whoisCh <- ip
}
return nil return nil
} }
func reconfigureDNSServer() (err error) { func reconfigureDNSServer() (err error) {
var newConf dnsforward.ServerConfig
tlsConf := &tlsConfigSettings{} tlsConf := &tlsConfigSettings{}
Context.tls.WriteDiskConfig(tlsConf) Context.tls.WriteDiskConfig(tlsConf)
newConf, err = generateServerConfig(tlsConf, httpRegister) newConf, err := newServerConfig(tlsConf, httpRegister)
if err != nil { if err != nil {
return fmt.Errorf("generating forwarding dns server config: %w", err) return fmt.Errorf("generating forwarding dns server config: %w", err)
} }
err = Context.dnsServer.Reconfigure(&newConf) err = Context.dnsServer.Reconfigure(newConf)
if err != nil { if err != nil {
return fmt.Errorf("starting forwarding dns server: %w", err) return fmt.Errorf("starting forwarding dns server: %w", err)
} }
addrProc := newClientAddrProcessor(config.Clients.Sources)
go addrProc.process(newConf.ClientIPs)
return nil return nil
} }

View File

@@ -82,12 +82,6 @@ type homeContext struct {
client *http.Client client *http.Client
appSignalChannel chan os.Signal // Channel for receiving OS signals by the console app appSignalChannel chan os.Signal // Channel for receiving OS signals by the console app
// rdnsCh is the channel for receiving IPs for rDNS processing.
rdnsCh chan netip.Addr
// whoisCh is the channel for receiving IPs for WHOIS processing.
whoisCh chan netip.Addr
// tlsCipherIDs are the ID of the cipher suites that AdGuard Home must use. // tlsCipherIDs are the ID of the cipher suites that AdGuard Home must use.
tlsCipherIDs []uint16 tlsCipherIDs []uint16
@@ -634,10 +628,10 @@ func run(opts options, clientBuildFS fs.FS) {
Context.tls.start() Context.tls.start()
go func() { go func() {
sErr := startDNSServer() startErr := startDNSServer()
if sErr != nil { if startErr != nil {
closeDNSServer() closeDNSServer()
fatalOnError(sErr) fatalOnError(startErr)
} }
}() }()