Skip to content

Commit

Permalink
+ dnsforward: respond to PTR requests for internal IP addresses
Browse files Browse the repository at this point in the history
{[IP] => "host"} <- DNSforward <-(leases)-- DHCP
  • Loading branch information
szolin committed Jun 1, 2020
1 parent 72f253f commit c2654ab
Show file tree
Hide file tree
Showing 8 changed files with 184 additions and 85 deletions.
36 changes: 27 additions & 9 deletions dnsforward/dnsforward.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -43,11 +44,14 @@ 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

tableReverse map[string]string // "IP -> hostname" table for reverse lookup

// DNS proxy instance for internal usage
// We don't Start() it and so no listen port is required.
Expand All @@ -59,13 +63,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
Expand Down
4 changes: 2 additions & 2 deletions dnsforward/dnsforward_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -496,7 +496,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}
Expand Down Expand Up @@ -645,7 +645,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"}
Expand Down
67 changes: 67 additions & 0 deletions dnsforward/handle_dns.go
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -88,11 +92,74 @@ 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.tableReverse = m
}

// 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 ||
s.tableReverse == nil {
return resultDone
}

arpa := req.Question[0].Name
arpa = strings.ToLower(arpa)
ip := util.DNSUnreverseAddr(arpa)
if ip == nil {
return resultDone
}

host, ok := s.tableReverse[ip.String()]
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.
Expand Down
8 changes: 7 additions & 1 deletion home/dns.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,13 @@ func initDNSServer() error {
filterConf.HTTPRegister = httpRegister
Context.dnsFilter = dnsfilter.New(&filterConf, nil)

Context.dnsServer = dnsforward.NewServer(Context.dnsFilter, Context.stats, Context.queryLog)
p := dnsforward.DNSCreateParams{
DNSFilter: Context.dnsFilter,
Stats: Context.stats,
QueryLog: Context.queryLog,
DHCPServer: Context.dhcpServer,
}
Context.dnsServer = dnsforward.NewServer(p)
dnsConfig := generateServerConfig()
err = Context.dnsServer.Prepare(&dnsConfig)
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion home/whois_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (

func prepareTestDNSServer() error {
config.DNS.Port = 1234
Context.dnsServer = dnsforward.NewServer(nil, nil, nil)
Context.dnsServer = dnsforward.NewServer(dnsforward.DNSCreateParams{})
conf := &dnsforward.ServerConfig{}
uc, err := proxy.ParseUpstreamsConfig([]string{"1.1.1.1"}, nil, time.Second*5)
if err != nil {
Expand Down
66 changes: 1 addition & 65 deletions util/auto_hosts.go
Original file line number Diff line number Diff line change
Expand Up @@ -282,78 +282,14 @@ func (a *AutoHosts) Process(host string, qtype uint16) []net.IP {
return ipsCopy
}

// convert character to hex number
func charToHex(n byte) int8 {
if n >= '0' && n <= '9' {
return int8(n) - '0'
} else if (n|0x20) >= 'a' && (n|0x20) <= 'f' {
return (int8(n) | 0x20) - 'a' + 10
}
return -1
}

// parse IPv6 reverse address
func ipParseArpa6(s string) net.IP {
if len(s) != 63 {
return nil
}
ip6 := make(net.IP, 16)

for i := 0; i != 64; i += 4 {

// parse "0.1."
n := charToHex(s[i])
n2 := charToHex(s[i+2])
if s[i+1] != '.' || (i != 60 && s[i+3] != '.') ||
n < 0 || n2 < 0 {
return nil
}

ip6[16-i/4-1] = byte(n2<<4) | byte(n&0x0f)
}
return ip6
}

// ipReverse - reverse IP address: 1.0.0.127 -> 127.0.0.1
func ipReverse(ip net.IP) net.IP {
n := len(ip)
r := make(net.IP, n)
for i := 0; i != n; i++ {
r[i] = ip[n-i-1]
}
return r
}

// Convert reversed ARPA address to a normal IP address
func dnsUnreverseAddr(s string) net.IP {
const arpaV4 = ".in-addr.arpa"
const arpaV6 = ".ip6.arpa"

if strings.HasSuffix(s, arpaV4) {
ip := strings.TrimSuffix(s, arpaV4)
ip4 := net.ParseIP(ip).To4()
if ip4 == nil {
return nil
}

return ipReverse(ip4)

} else if strings.HasSuffix(s, arpaV6) {
ip := strings.TrimSuffix(s, arpaV6)
return ipParseArpa6(ip)
}

return nil // unknown suffix
}

// ProcessReverse - process PTR request
// Return "" if not found or an error occurred
func (a *AutoHosts) ProcessReverse(addr string, qtype uint16) string {
if qtype != dns.TypePTR {
return ""
}

ipReal := dnsUnreverseAddr(addr)
ipReal := DNSUnreverseAddr(addr)
if ipReal == nil {
return "" // invalid IP in question
}
Expand Down
16 changes: 9 additions & 7 deletions util/auto_hosts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,13 @@ func TestAutoHostsFSNotify(t *testing.T) {
}

func TestIP(t *testing.T) {
assert.True(t, dnsUnreverseAddr("1.0.0.127.in-addr.arpa").Equal(net.ParseIP("127.0.0.1").To4()))
assert.True(t, dnsUnreverseAddr("4.3.2.1.d.c.b.a.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa").Equal(net.ParseIP("::abcd:1234")))

assert.True(t, dnsUnreverseAddr("1.0.0.127.in-addr.arpa.") == nil)
assert.True(t, dnsUnreverseAddr(".0.0.127.in-addr.arpa") == nil)
assert.True(t, dnsUnreverseAddr(".3.2.1.d.c.b.a.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa") == nil)
assert.True(t, dnsUnreverseAddr("4.3.2.1.d.c.b.a.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0..ip6.arpa") == nil)
assert.Equal(t, "127.0.0.1", DNSUnreverseAddr("1.0.0.127.in-addr.arpa").String())
assert.Equal(t, "::abcd:1234", DNSUnreverseAddr("4.3.2.1.d.c.b.a.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa").String())
assert.Equal(t, "::abcd:1234", DNSUnreverseAddr("4.3.2.1.d.c.B.A.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa").String())

assert.Nil(t, DNSUnreverseAddr("1.0.0.127.in-addr.arpa."))
assert.Nil(t, DNSUnreverseAddr(".0.0.127.in-addr.arpa"))
assert.Nil(t, DNSUnreverseAddr(".3.2.1.d.c.b.a.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa"))
assert.Nil(t, DNSUnreverseAddr("4.3.2.1.d.c.b.a.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0..ip6.arpa"))
assert.Nil(t, DNSUnreverseAddr("4.3.2.1.d.c.b. .0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa"))
}
70 changes: 70 additions & 0 deletions util/dns.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package util

import (
"net"
"strings"
)

// convert character to hex number
func charToHex(n byte) int8 {
if n >= '0' && n <= '9' {
return int8(n) - '0'
} else if (n|0x20) >= 'a' && (n|0x20) <= 'f' {
return (int8(n) | 0x20) - 'a' + 10
}
return -1
}

// parse IPv6 reverse address
func ipParseArpa6(s string) net.IP {
if len(s) != 63 {
return nil
}
ip6 := make(net.IP, 16)

for i := 0; i != 64; i += 4 {

// parse "0.1."
n := charToHex(s[i])
n2 := charToHex(s[i+2])
if s[i+1] != '.' || (i != 60 && s[i+3] != '.') ||
n < 0 || n2 < 0 {
return nil
}

ip6[16-i/4-1] = byte(n2<<4) | byte(n&0x0f)
}
return ip6
}

// ipReverse - reverse IP address: 1.0.0.127 -> 127.0.0.1
func ipReverse(ip net.IP) net.IP {
n := len(ip)
r := make(net.IP, n)
for i := 0; i != n; i++ {
r[i] = ip[n-i-1]
}
return r
}

// DNSUnreverseAddr - convert reversed ARPA address to a normal IP address
func DNSUnreverseAddr(s string) net.IP {
const arpaV4 = ".in-addr.arpa"
const arpaV6 = ".ip6.arpa"

if strings.HasSuffix(s, arpaV4) {
ip := strings.TrimSuffix(s, arpaV4)
ip4 := net.ParseIP(ip).To4()
if ip4 == nil {
return nil
}

return ipReverse(ip4)

} else if strings.HasSuffix(s, arpaV6) {
ip := strings.TrimSuffix(s, arpaV6)
return ipParseArpa6(ip)
}

return nil // unknown suffix
}

0 comments on commit c2654ab

Please sign in to comment.