dns: serve reverse lookup queries for all RFC 1918 reserved networks

This commit is contained in:
Michael Stapelberg 2018-06-14 20:24:44 +02:00
parent fdd2201ef5
commit 02c7fa7e0d
2 changed files with 91 additions and 19 deletions

View File

@ -2,6 +2,7 @@ package dns
import ( import (
"log" "log"
"net"
"strings" "strings"
"time" "time"
@ -45,6 +46,50 @@ func (s *Server) SetLeases(leases []dhcp4d.Lease) {
} }
} }
func mustParseCIDR(s string) *net.IPNet {
_, ipnet, err := net.ParseCIDR(s)
if err != nil {
panic(err)
}
return ipnet
}
var (
localNets = []*net.IPNet{
// reversed: https://tools.ietf.org/html/rfc1918#section-3
mustParseCIDR("10.0.0.0/8"),
mustParseCIDR("172.16.0.0/12"),
mustParseCIDR("192.168.0.0/16"),
}
)
func reverse(ss []string) {
last := len(ss) - 1
for i := 0; i < len(ss)/2; i++ {
ss[i], ss[last-i] = ss[last-i], ss[i]
}
}
func isLocalInAddrArpa(q string) bool {
if !strings.HasSuffix(q, ".in-addr.arpa.") {
return false
}
parts := strings.Split(strings.TrimSuffix(q, ".in-addr.arpa."), ".")
reverse(parts)
ip := net.ParseIP(strings.Join(parts, "."))
if ip == nil {
return false
}
var local bool
for _, l := range localNets {
if l.Contains(ip) {
local = true
break
}
}
return local
}
// TODO: is handleRequest called in more than one goroutine at a time? // TODO: is handleRequest called in more than one goroutine at a time?
// TODO: require search domains to be present, then use HandleFunc("lan.", internalName) // TODO: require search domains to be present, then use HandleFunc("lan.", internalName)
func (s *Server) handleRequest(w dns.ResponseWriter, r *dns.Msg) { func (s *Server) handleRequest(w dns.ResponseWriter, r *dns.Msg) {
@ -69,7 +114,7 @@ func (s *Server) handleRequest(w dns.ResponseWriter, r *dns.Msg) {
} }
} }
if q.Qtype == dns.TypePTR && q.Qclass == dns.ClassINET { if q.Qtype == dns.TypePTR && q.Qclass == dns.ClassINET {
if strings.HasSuffix(q.Name, "168.192.in-addr.arpa.") { if isLocalInAddrArpa(q.Name) {
if host, ok := s.hostsByIP[q.Name]; ok { if host, ok := s.hostsByIP[q.Name]; ok {
rr, err := dns.NewRR(q.Name + " 3600 IN PTR " + host + "." + s.domain) rr, err := dns.NewRR(q.Name + " 3600 IN PTR " + host + "." + s.domain)
if err != nil { if err != nil {

View File

@ -75,26 +75,53 @@ func TestDHCP(t *testing.T) {
} }
func TestDHCPReverse(t *testing.T) { func TestDHCPReverse(t *testing.T) {
r := &recorder{} for _, test := range []struct {
s := NewServer("localhost:0", "lan") ip net.IP
s.SetLeases([]dhcp4d.Lease{ question string
}{
{ {
Hostname: "xps", ip: net.IP{192, 168, 42, 23},
Addr: net.IP{192, 168, 42, 23}, question: "23.42.168.192.in-addr.arpa.",
}, },
})
m := new(dns.Msg) {
m.SetQuestion("23.42.168.192.in-addr.arpa.", dns.TypePTR) ip: net.IP{10, 0, 0, 2},
s.handleRequest(r, m) question: "2.0.0.10.in-addr.arpa.",
if got, want := len(r.response.Answer), 1; got != want { },
t.Fatalf("unexpected number of answers: got %d, want %d", got, want)
} {
a := r.response.Answer[0] ip: net.IP{172, 16, 0, 1}, // 172.16/12 HostMin
if _, ok := a.(*dns.PTR); !ok { question: "1.0.16.172.in-addr.arpa.",
t.Fatalf("unexpected response type: got %T, want dns.A", a) },
}
if got, want := a.(*dns.PTR).Ptr, "xps.lan."; got != want { {
t.Fatalf("unexpected response record: got %q, want %q", got, want) ip: net.IP{172, 31, 255, 254}, // 172.16/12 HostMax
question: "254.255.31.172.in-addr.arpa.",
},
} {
t.Run(test.question, func(t *testing.T) {
r := &recorder{}
s := NewServer("localhost:0", "lan")
s.SetLeases([]dhcp4d.Lease{
{
Hostname: "xps",
Addr: test.ip,
},
})
m := new(dns.Msg)
m.SetQuestion(test.question, dns.TypePTR)
s.handleRequest(r, m)
if got, want := len(r.response.Answer), 1; got != want {
t.Fatalf("unexpected number of answers: got %d, want %d", got, want)
}
a := r.response.Answer[0]
if _, ok := a.(*dns.PTR); !ok {
t.Fatalf("unexpected response type: got %T, want dns.A", a)
}
if got, want := a.(*dns.PTR).Ptr, "xps.lan."; got != want {
t.Fatalf("unexpected response record: got %q, want %q", got, want)
}
})
} }
} }