Skip to content

Commit

Permalink
dnsforward: extract subnet from arpa
Browse files Browse the repository at this point in the history
  • Loading branch information
EugeneOne1 committed Mar 14, 2023
1 parent c670644 commit 17df1a0
Show file tree
Hide file tree
Showing 2 changed files with 183 additions and 15 deletions.
75 changes: 60 additions & 15 deletions internal/dnsforward/dns.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/binary"
"net"
"net/netip"
"strconv"
"strings"
"time"

Expand Down Expand Up @@ -442,6 +443,59 @@ func (s *Server) processDHCPHosts(dctx *dnsContext) (rc resultCode) {
return resultCodeSuccess
}

// extractARPASubnet tries to convert a reversed ARPA address being a part of
// domain to an IP network. arpa can be domain name or an FQDN.
//
// TODO(e.burkov): Move to golibs.
func extractARPASubnet(domain string) (subnet *net.IPNet, err error) {
const (
arpaSuffix = ".arpa"
v4Suffix = ".in-addr"
v6Suffix = ".ip6"
)

domain = strings.ToLower(domain)
domain = strings.TrimSuffix(domain, ".")
if !strings.HasSuffix(domain, arpaSuffix) {
return nil, nil
}

leftmostIdx := len(domain) - len(arpaSuffix)

labelsNum := 0
base := 0
bitSize := 0
switch head := domain[:leftmostIdx]; {
case strings.HasSuffix(head, v4Suffix):
leftmostIdx -= len(v4Suffix)
labelsNum = net.IPv4len
base = 10
bitSize = 8
case strings.HasSuffix(head, v6Suffix):
leftmostIdx -= len(v6Suffix)
// Reversed IPv6 consists of nibbles, which is a half of byte.
labelsNum = net.IPv6len * 2
base = 16
bitSize = 4
default:
return nil, nil
}

for labelsNum > 0 && leftmostIdx > 0 {
idx := strings.LastIndexByte(domain[:leftmostIdx], '.')
_, parseErr := strconv.ParseUint(domain[idx+1:leftmostIdx], base, bitSize)
if parseErr != nil {
break
}

leftmostIdx = idx
labelsNum--
}
leftmostIdx += 1

return netutil.SubnetFromReversedAddr(domain[leftmostIdx:])
}

// processRestrictLocal responds with NXDOMAIN to PTR requests for IP addresses
// in locally served network from external clients.
func (s *Server) processRestrictLocal(dctx *dnsContext) (rc resultCode) {
Expand All @@ -453,21 +507,12 @@ func (s *Server) processRestrictLocal(dctx *dnsContext) (rc resultCode) {
return resultCodeSuccess
}

ip, err := netutil.IPFromReversedAddr(q.Name)
subnet, err := extractARPASubnet(q.Name)
if err != nil {
log.Debug("dnsforward: parsing reversed addr: %s", err)

// DNS-Based Service Discovery uses PTR records having not an ARPA
// format of the domain name in question. Those shouldn't be
// invalidated. See http://www.dns-sd.org/ServerStaticSetup.html and
// RFC 2782.
name := strings.TrimSuffix(q.Name, ".")
if err = netutil.ValidateSRVDomainName(name); err != nil {
log.Debug("dnsforward: validating service domain: %s", err)

return resultCodeError
}

return resultCodeError
} else if subnet == nil {
log.Debug("dnsforward: request is not for arpa domain")

return resultCodeSuccess
Expand All @@ -476,11 +521,11 @@ func (s *Server) processRestrictLocal(dctx *dnsContext) (rc resultCode) {
// Restrict an access to local addresses for external clients. We also
// assume that all the DHCP leases we give are locally served or at least
// shouldn't be accessible externally.
if !s.privateNets.Contains(ip) {
if !s.privateNets.Contains(subnet.IP) {
return resultCodeSuccess
}

log.Debug("dnsforward: addr %s is from locally served network", ip)
log.Debug("dnsforward: addr %s is from locally served network", subnet.IP)

if !dctx.isLocalClient {
log.Debug("dnsforward: %q requests an internal ip", pctx.Addr)
Expand All @@ -491,7 +536,7 @@ func (s *Server) processRestrictLocal(dctx *dnsContext) (rc resultCode) {
}

// Do not perform unreversing ever again.
dctx.unreversedReqIP = ip
dctx.unreversedReqIP = subnet.IP

// There is no need to filter request from external addresses since this
// code is only executed when the request is for locally served ARPA
Expand Down
123 changes: 123 additions & 0 deletions internal/dnsforward/dns_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -605,3 +605,126 @@ func TestIPStringFromAddr(t *testing.T) {
assert.Empty(t, ipStringFromAddr(nil))
})
}

func TestExtractARPASubnet(t *testing.T) {
const (
ipv4Suffix = `.in-addr.arpa`
ipv4RevPart = `2.1` + ipv4Suffix
ipv4RevGood = `4.3.` + ipv4RevPart

ipv6Suffix = `.ip6.arpa`
ipv6RevPart = `4.3.2.1.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa`
ipv6RevGood = `f.e.d.c.0.0.0.0.0.0.0.0.0.0.0.0.` + ipv6RevPart
)

testIPv4 := net.IP{1, 2, 3, 4}
testIPv4Part := net.IP{1, 2, 0, 0}
testIPv6 := net.IP{
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x12, 0x34,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xcd, 0xef,
}
testIPv6Part := net.IP{
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x12, 0x34,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
}

testCases := []struct {
want *net.IPNet
name string
domain string
}{{
want: nil,
name: "not_an_arpa_fqdn",
domain: "some.domain.name.",
}, {
want: &net.IPNet{
IP: testIPv4,
Mask: net.CIDRMask(netutil.IPv4BitLen, netutil.IPv4BitLen),
},
name: "full_v4",
domain: ipv4RevGood,
}, {
want: &net.IPNet{
IP: testIPv4Part,
Mask: net.CIDRMask(16, netutil.IPv4BitLen),
},
name: "half_v4",
domain: ipv4RevPart,
}, {
want: &net.IPNet{
IP: testIPv4,
Mask: net.CIDRMask(netutil.IPv4BitLen, netutil.IPv4BitLen),
},
name: "full_v4_part",
domain: "a." + ipv4RevGood,
}, {
want: &net.IPNet{
IP: testIPv4Part,
Mask: net.CIDRMask(16, netutil.IPv4BitLen),
},
name: "partial_v4_part",
domain: "a." + ipv4RevPart,
}, {
want: &net.IPNet{
IP: testIPv4Part,
Mask: net.CIDRMask(16, netutil.IPv4BitLen),
},
name: "almost_full_v4",
domain: "256." + ipv4RevPart,
}, {
want: &net.IPNet{
IP: testIPv4Part,
Mask: net.CIDRMask(16, netutil.IPv4BitLen),
},
name: "almost_full_v4_part",
domain: "a.256." + ipv4RevPart,
}, {
want: nil,
name: "empty_v4",
domain: ipv4Suffix[1:],
}, {
want: &net.IPNet{
IP: testIPv6,
Mask: net.CIDRMask(netutil.IPv6BitLen, netutil.IPv6BitLen),
},
name: "full_v6",
domain: ipv6RevGood,
}, {
want: &net.IPNet{
IP: testIPv6Part,
Mask: net.CIDRMask(64, netutil.IPv6BitLen),
},
name: "half_v6",
domain: ipv6RevPart,
}, {
want: &net.IPNet{
IP: testIPv6,
Mask: net.CIDRMask(netutil.IPv6BitLen, netutil.IPv6BitLen),
},
name: "full_v6_part",
domain: "g." + ipv6RevGood,
}, {
want: &net.IPNet{
IP: testIPv6Part,
Mask: net.CIDRMask(64, netutil.IPv6BitLen),
},
name: "partial_v6_part",
domain: "g." + ipv6RevPart,
}, {
want: nil,
name: "empty_v6",
domain: "ip6.arpa",
}}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
subnet, err := extractARPASubnet(tc.domain)
require.NoError(t, err)
assert.Equal(t, tc.want, subnet)
})
}
}

0 comments on commit 17df1a0

Please sign in to comment.