+ dns: respond to PTR requests for internal IP addresses from DHCP

Close #1682

Squashed commit of the following:

commit 2fad3544bf8853b1f8f19ad8b7bc8a490c96e533
Author: Simon Zolin <s.zolin@adguard.com>
Date:   Mon Jun 22 17:32:45 2020 +0300

    minor

commit 7c17992424702d95e6de91f30e8ae2dfcd8de257
Author: Simon Zolin <s.zolin@adguard.com>
Date:   Mon Jun 22 16:09:34 2020 +0300

    build

commit 16a52e11a015a97d3cbf30362482a4abd052192b
Merge: 7b6a73c8 2c47053c
Author: Simon Zolin <s.zolin@adguard.com>
Date:   Mon Jun 22 16:08:32 2020 +0300

    Merge remote-tracking branch 'origin/master' into 1682-dhcp-resolve

commit 7b6a73c84b5cb9a073a9dfb7d7bdecd22e1e1318
Author: Simon Zolin <s.zolin@adguard.com>
Date:   Mon Jun 22 16:01:34 2020 +0300

    tests

commit c2654abb2e5e7b7e3a04e4ddb8e1064b37613929
Author: Simon Zolin <s.zolin@adguard.com>
Date:   Mon Jun 1 15:15:13 2020 +0300

    + dnsforward: respond to PTR requests for internal IP addresses

    {[IP] => "host"} <- DNSforward <-(leases)-- DHCP
This commit is contained in:
Simon Zolin
2020-06-23 12:13:13 +03:00
parent 2c47053cfe
commit 49a92605b8
9 changed files with 236 additions and 89 deletions

View File

@@ -8,6 +8,7 @@ import (
"sync"
"time"
"github.com/AdguardTeam/AdGuardHome/dhcpd"
"github.com/AdguardTeam/AdGuardHome/dnsfilter"
"github.com/AdguardTeam/AdGuardHome/querylog"
"github.com/AdguardTeam/AdGuardHome/stats"
@@ -43,11 +44,15 @@ var webRegistered bool
//
// The zero Server is empty and ready for use.
type Server struct {
dnsProxy *proxy.Proxy // DNS proxy instance
dnsFilter *dnsfilter.Dnsfilter // DNS filter instance
queryLog querylog.QueryLog // Query log instance
stats stats.Stats
access *accessCtx
dnsProxy *proxy.Proxy // DNS proxy instance
dnsFilter *dnsfilter.Dnsfilter // DNS filter instance
dhcpServer *dhcpd.Server // DHCP server instance (optional)
queryLog querylog.QueryLog // Query log instance
stats stats.Stats
access *accessCtx
tablePTR map[string]string // "IP -> hostname" table for reverse lookup
tablePTRLock sync.Mutex
// DNS proxy instance for internal usage
// We don't Start() it and so no listen port is required.
@@ -59,13 +64,27 @@ type Server struct {
conf ServerConfig
}
// DNSCreateParams - parameters for NewServer()
type DNSCreateParams struct {
DNSFilter *dnsfilter.Dnsfilter
Stats stats.Stats
QueryLog querylog.QueryLog
DHCPServer *dhcpd.Server
}
// NewServer creates a new instance of the dnsforward.Server
// Note: this function must be called only once
func NewServer(dnsFilter *dnsfilter.Dnsfilter, stats stats.Stats, queryLog querylog.QueryLog) *Server {
func NewServer(p DNSCreateParams) *Server {
s := &Server{}
s.dnsFilter = dnsFilter
s.stats = stats
s.queryLog = queryLog
s.dnsFilter = p.DNSFilter
s.stats = p.Stats
s.queryLog = p.QueryLog
s.dhcpServer = p.DHCPServer
if s.dhcpServer != nil {
s.dhcpServer.SetOnLeaseChanged(s.onDHCPLeaseChanged)
s.onDHCPLeaseChanged(dhcpd.LeaseChangedAdded)
}
if runtime.GOARCH == "mips" || runtime.GOARCH == "mipsle" {
// Use plain DNS on MIPS, encryption is too slow

View File

@@ -15,6 +15,7 @@ import (
"testing"
"time"
"github.com/AdguardTeam/AdGuardHome/dhcpd"
"github.com/AdguardTeam/AdGuardHome/dnsfilter"
"github.com/AdguardTeam/dnsproxy/proxy"
"github.com/AdguardTeam/dnsproxy/upstream"
@@ -496,7 +497,7 @@ func TestBlockedCustomIP(t *testing.T) {
c := dnsfilter.Config{}
f := dnsfilter.New(&c, filters)
s := NewServer(f, nil, nil)
s := NewServer(DNSCreateParams{DNSFilter: f})
conf := ServerConfig{}
conf.UDPListenAddr = &net.UDPAddr{Port: 0}
conf.TCPListenAddr = &net.TCPAddr{Port: 0}
@@ -648,7 +649,7 @@ func TestRewrite(t *testing.T) {
}
f := dnsfilter.New(&c, nil)
s := NewServer(f, nil, nil)
s := NewServer(DNSCreateParams{DNSFilter: f})
conf := ServerConfig{}
conf.UDPListenAddr = &net.UDPAddr{Port: 0}
conf.TCPListenAddr = &net.TCPAddr{Port: 0}
@@ -705,7 +706,7 @@ func createTestServer(t *testing.T) *Server {
c.CacheTime = 30
f := dnsfilter.New(&c, filters)
s := NewServer(f, nil, nil)
s := NewServer(DNSCreateParams{DNSFilter: f})
s.conf.UDPListenAddr = &net.UDPAddr{Port: 0}
s.conf.TCPListenAddr = &net.TCPAddr{Port: 0}
s.conf.UpstreamDNS = []string{"8.8.8.8:53", "8.8.4.4:53"}
@@ -1012,3 +1013,39 @@ func TestMatchDNSName(t *testing.T) {
assert.True(t, !matchDNSName(dnsNames, ""))
assert.True(t, !matchDNSName(dnsNames, "*.host2"))
}
func TestPTRResponse(t *testing.T) {
dhcp := &dhcpd.Server{}
dhcp.IPpool = make(map[[4]byte]net.HardwareAddr)
c := dnsfilter.Config{}
f := dnsfilter.New(&c, nil)
s := NewServer(DNSCreateParams{DNSFilter: f, DHCPServer: dhcp})
s.conf.UDPListenAddr = &net.UDPAddr{Port: 0}
s.conf.TCPListenAddr = &net.TCPAddr{Port: 0}
s.conf.UpstreamDNS = []string{"127.0.0.1:53"}
s.conf.FilteringConfig.ProtectionEnabled = true
err := s.Prepare(nil)
assert.True(t, err == nil)
assert.Nil(t, s.Start())
l := dhcpd.Lease{}
l.IP = net.ParseIP("127.0.0.1").To4()
l.HWAddr, _ = net.ParseMAC("aa:aa:aa:aa:aa:aa")
l.Hostname = "localhost"
dhcp.AddStaticLease(l)
addr := s.dnsProxy.Addr(proxy.ProtoUDP)
req := createTestMessage("1.0.0.127.in-addr.arpa.")
req.Question[0].Qtype = dns.TypePTR
resp, err := dns.Exchange(req, addr.String())
assert.Nil(t, err)
assert.Equal(t, 1, len(resp.Answer))
assert.Equal(t, dns.TypePTR, resp.Answer[0].Header().Rrtype)
assert.Equal(t, "1.0.0.127.in-addr.arpa.", resp.Answer[0].Header().Name)
ptr := resp.Answer[0].(*dns.PTR)
assert.Equal(t, "localhost.", ptr.Ptr)
s.Close()
}

View File

@@ -1,9 +1,12 @@
package dnsforward
import (
"strings"
"time"
"github.com/AdguardTeam/AdGuardHome/dhcpd"
"github.com/AdguardTeam/AdGuardHome/dnsfilter"
"github.com/AdguardTeam/AdGuardHome/util"
"github.com/AdguardTeam/dnsproxy/proxy"
"github.com/AdguardTeam/golibs/log"
"github.com/miekg/dns"
@@ -39,6 +42,7 @@ func (s *Server) handleDNSRequest(_ *proxy.Proxy, d *proxy.DNSContext) error {
type modProcessFunc func(ctx *dnsContext) int
mods := []modProcessFunc{
processInitial,
processInternalIPAddrs,
processFilteringBeforeRequest,
processUpstream,
processDNSSECAfterResponse,
@@ -88,11 +92,82 @@ func processInitial(ctx *dnsContext) int {
return resultDone
}
func (s *Server) onDHCPLeaseChanged(flags int) {
switch flags {
case dhcpd.LeaseChangedAdded,
dhcpd.LeaseChangedAddedStatic,
dhcpd.LeaseChangedRemovedStatic:
//
default:
return
}
m := make(map[string]string)
ll := s.dhcpServer.Leases(dhcpd.LeasesAll)
for _, l := range ll {
if len(l.Hostname) == 0 {
continue
}
m[l.IP.String()] = l.Hostname
}
log.Debug("DNS: added %d PTR entries from DHCP", len(m))
s.tablePTRLock.Lock()
s.tablePTR = m
s.tablePTRLock.Unlock()
}
// Respond to PTR requests if the target IP address is leased by our DHCP server
func processInternalIPAddrs(ctx *dnsContext) int {
s := ctx.srv
req := ctx.proxyCtx.Req
if req.Question[0].Qtype != dns.TypePTR {
return resultDone
}
arpa := req.Question[0].Name
arpa = strings.TrimSuffix(arpa, ".")
arpa = strings.ToLower(arpa)
ip := util.DNSUnreverseAddr(arpa)
if ip == nil {
return resultDone
}
s.tablePTRLock.Lock()
if s.tablePTR == nil {
s.tablePTRLock.Unlock()
return resultDone
}
host, ok := s.tablePTR[ip.String()]
s.tablePTRLock.Unlock()
if !ok {
return resultDone
}
log.Debug("DNS: reverse-lookup: %s -> %s", arpa, host)
resp := s.makeResponse(req)
ptr := &dns.PTR{}
ptr.Hdr = dns.RR_Header{
Name: req.Question[0].Name,
Rrtype: dns.TypePTR,
Ttl: s.conf.BlockedResponseTTL,
Class: dns.ClassINET,
}
ptr.Ptr = host + "."
resp.Answer = append(resp.Answer, ptr)
ctx.proxyCtx.Res = resp
return resultDone
}
// Apply filtering logic
func processFilteringBeforeRequest(ctx *dnsContext) int {
s := ctx.srv
d := ctx.proxyCtx
if d.Res != nil {
return resultDone // response is already set - nothing to do
}
s.RLock()
// Synchronize access to s.dnsFilter so it won't be suddenly uninitialized while in use.
// This could happen after proxy server has been stopped, but its workers are not yet exited.