Skip to content

Commit

Permalink
Pull request 104: Allow empty ARPA
Browse files Browse the repository at this point in the history
Squashed commit of the following:

commit 33cce10
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Apr 1 18:42:40 2024 +0300

    netutil: imp tests

commit e221b7e
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Apr 1 18:28:00 2024 +0300

    netutil: imp performance

commit 83b7eba
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Apr 1 17:39:37 2024 +0300

    netutil: imp code, docs

commit 2621db3
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Apr 1 14:15:09 2024 +0300

    netutil: add bench results

commit 06ea584
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Mon Apr 1 14:10:00 2024 +0300

    netutil: add bench for extract

commit dcc2bc4
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Sun Mar 31 11:37:38 2024 +0300

    netutil: imp code

commit 5107383
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date:   Sat Mar 30 17:17:30 2024 +0300

    netutil: allow empty arpa, fix panic
  • Loading branch information
EugeneOne1 committed Apr 1, 2024
1 parent 661cdcc commit 1281448
Show file tree
Hide file tree
Showing 3 changed files with 322 additions and 155 deletions.
187 changes: 120 additions & 67 deletions netutil/reversed.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package netutil

import (
"math"
"net"
"net/netip"
"strconv"
Expand Down Expand Up @@ -40,7 +41,7 @@ const (
//
// An example of IPv4 with a maximum length:
//
// 49.91.20.104.in-addr.arpa
// 255.255.255.255.in-addr.arpa
//
// An example of IPv6 with a maximum length:
//
Expand All @@ -55,12 +56,9 @@ const (

// reverseIPv4 inverts the order of bytes in an IP address.
func reverseIPv4(ip [4]byte) (out [4]byte) {
l := len(ip)
for i := range ip[:l/2] {
ip[i], ip[l-i-1] = ip[l-i-1], ip[i]
}
out[0], out[1], out[2], out[3] = ip[3], ip[2], ip[1], ip[0]

return ip
return out
}

// ipv4FromReversed parses an IPv4 reverse address. It assumes that arpa is a
Expand All @@ -70,11 +68,14 @@ func ipv4FromReversed(arpa string) (addr netip.Addr, err error) {
if err != nil {
// Don't wrap the error, since it's already informative enough as is.
return netip.Addr{}, err
} else if !arpaAddr.Is4() {
return netip.Addr{}, &AddrError{
Kind: AddrKindIPv4,
Addr: arpa,
}
}

ip := reverseIPv4(arpaAddr.As4())

return netip.AddrFrom4(ip), nil
return netip.AddrFrom4(reverseIPv4(arpaAddr.As4())), nil
}

// ipv6FromReversed parses an IPv6 reverse address. It assumes that arpa is a
Expand Down Expand Up @@ -134,26 +135,25 @@ func IPFromReversedAddr(arpa string) (addr netip.Addr, err error) {

// TODO(a.garipov): Add stringutil.HasSuffixFold and remove this.
arpa = strings.ToLower(arpa)

if strings.HasSuffix(arpa, arpaV4Suffix) {
switch {
case strings.HasSuffix(arpa, arpaV4Suffix):
ipStr := arpa[:len(arpa)-len(arpaV4Suffix)]

return ipv4FromReversed(ipStr)
}

if strings.HasSuffix(arpa, arpaV6Suffix) {
if l := len(arpa); l != arpaV6MaxLen {
return netip.Addr{}, &LengthError{
Kind: AddrKindARPA,
Allowed: []int{arpaV6MaxLen},
Length: l,
}
case strings.HasSuffix(arpa, arpaV6Suffix):
l := len(arpa)
if l == arpaV6MaxLen {
return ipv6FromReversed(arpa)
}

return ipv6FromReversed(arpa)
return netip.Addr{}, &LengthError{
Kind: AddrKindARPA,
Allowed: []int{arpaV6MaxLen},
Length: l,
}
default:
return netip.Addr{}, ErrNotAReversedIP
}

return netip.Addr{}, ErrNotAReversedIP
}

// IPToReversedAddr returns the reversed ARPA address of ip suitable for reverse
Expand All @@ -169,13 +169,13 @@ func IPToReversedAddr(ip net.IP) (arpa string, err error) {
var writeByte func(val byte)
b := &strings.Builder{}
if ip4 := ip.To4(); ip4 != nil {
l, suffix = arpaV4MaxLen, arpaV4Suffix[1:]
l, suffix = arpaV4MaxLen, arpaV4Suffix[len("."):]
ip = ip4
writeByte = func(val byte) {
stringutil.WriteToBuilder(b, strconv.Itoa(int(val)), dot)
}
} else if ip6 := ip.To16(); ip6 != nil {
l, suffix = arpaV6MaxLen, arpaV6Suffix[1:]
l, suffix = arpaV6MaxLen, arpaV6Suffix[len("."):]
ip = ip6
writeByte = func(val byte) {
stringutil.WriteToBuilder(
Expand Down Expand Up @@ -204,7 +204,8 @@ func IPToReversedAddr(ip net.IP) (arpa string, err error) {
}

// ipv4NetFromReversed parses an IPv4 reverse network. It assumes that arpa is
// a valid domain name and is not a domain name with a full IPv4 address.
// a domain containing up to 4 consequent IPv4 labels. An empty arpa is
// considered a zero prefix of an IPv4 family.
func ipv4NetFromReversed(arpa string) (pref netip.Prefix, err error) {
var octet64 uint64
var octetIdx int
Expand All @@ -225,6 +226,8 @@ func ipv4NetFromReversed(arpa string) (pref netip.Prefix, err error) {
// except an octet itself equals zero.
//
// See RFC 1035 Section 3.5.
//
// TODO(e.burkov): Use [RuneError]?
return netip.Prefix{}, &AddrError{
Err: errors.Error("leading zero is forbidden at this position"),
Kind: LabelKindDomain,
Expand All @@ -247,7 +250,9 @@ func ipv4NetFromReversed(arpa string) (pref netip.Prefix, err error) {
}

// ipv6NetFromReversed parses an IPv6 reverse network. It assumes that arpa is
// a valid domain name and is not a domain name with a full IPv6 address.
// a valid domain name and is not a domain name with a full IPv6 address. It
// support the root domain "ip6.arpa" and turns it into the zero prefix of an
// IPv6 family.
func ipv6NetFromReversed(arpa string) (pref netip.Prefix, err error) {
const nibbleLen = len("0.")

Expand Down Expand Up @@ -288,29 +293,45 @@ func ipv6NetFromReversed(arpa string) (pref netip.Prefix, err error) {
return netip.PrefixFrom(addr, l*4), nil
}

// subnetFromReversedV4 tries to convert arpa to a CIDR prefix. It expects
// arpa to be a valid domain name in a lower case.
// subnetFromReversedV4 tries to convert arpa to a CIDR prefix. It expects arpa
// to be a valid domain name in a lower case. The root domain "in-addr.arpa" is
// considered valid and turns into the zero prefix of an IPv4 family.
func subnetFromReversedV4(arpa string) (subnet netip.Prefix, err error) {
arpa = arpa[:len(arpa)-len(arpaV4Suffix)]
// Cut "in-addr.arpa", but keep the possible dot.
l := len(arpa) - len(arpaV4Suffix) + len(".")
arpa = arpa[:l]

if dots := strings.Count(arpa, "."); dots > 3 {
switch {
case l == 0:
// Go on.
case !strings.HasSuffix(arpa, "."):
return netip.Prefix{}, ErrNotAReversedSubnet
} else if dots == 3 {
var addr netip.Addr
addr, err = ipv4FromReversed(arpa)
if err != nil {
// Don't wrap the error, since it's already informative enough as is.
return netip.Prefix{}, err
default:
arpa = arpa[:l-1]
dots := strings.Count(arpa, ".")
if dots > 3 {
return netip.Prefix{}, ErrNotAReversedSubnet
}

return netip.PrefixFrom(addr, addr.BitLen()), nil
if dots == 3 {
var addr netip.Addr
addr, err = ipv4FromReversed(arpa)
if err != nil {
// Don't wrap the error, since it's already informative enough
// as is.
return netip.Prefix{}, err
}

return netip.PrefixFrom(addr, addr.BitLen()), nil
}
}

return ipv4NetFromReversed(arpa)
}

// subnetFromReversedV6 tries to convert arpa a CIDR prefix. It expects
// arpa to be a valid domain name in a lower case.
// subnetFromReversedV6 tries to convert arpa a CIDR prefix. It expects arpa to
// be a valid domain name in a lower case. The root domain "ip6.arpa" is
// considered valid and turns into the zero prefix of an IPv6 family.
func subnetFromReversedV6(arpa string) (subnet netip.Prefix, err error) {
if l := len(arpa); l == arpaV6MaxLen {
var addr netip.Addr
Expand All @@ -333,7 +354,9 @@ func subnetFromReversedV6(arpa string) (subnet netip.Prefix, err error) {
}

// PrefixFromReversedAddr tries to convert a reversed ARPA address to an IP
// address prefix. arpa can be domain name or an FQDN.
// address prefix. The root ARPA domains "in-addr.arpa" and "ip6.arpa" are
// considered valid and turn into the zero prefixes of the corresponding family.
// arpa can be domain name or an FQDN.
//
// Any error returned will have the underlying type of [*AddrError].
func PrefixFromReversedAddr(arpa string) (p netip.Prefix, err error) {
Expand All @@ -351,24 +374,48 @@ func PrefixFromReversedAddr(arpa string) (p netip.Prefix, err error) {
arpa = strings.ToLower(arpa)

switch {
case strings.HasSuffix(arpa, arpaV4Suffix):
case strings.HasSuffix(arpa, arpaV4Suffix[len("."):]):
return subnetFromReversedV4(arpa)
case strings.HasSuffix(arpa, arpaV6Suffix):
case strings.HasSuffix(arpa, arpaV6Suffix[len("."):]):
return subnetFromReversedV6(arpa)
default:
return netip.Prefix{}, ErrNotAReversedSubnet
}
}

// isV4ARPALabel reports whether label is a valid ARPA label for an IPv4
// address, i.e. a decimal number in the range [0, 255] with no leading zeros.
func isV4ARPALabel(label string) (ok bool) {
switch l := len(label); {
case l < 1, l > 3:
return false
case l == 1:
return label[0] >= '0' && label[0] <= '9'
case label[0] == '0':
return false
default:
val := 0
for _, c := range label {
if c < '0' || c > '9' {
return false
}

val = val*10 + int(c-'0')
}

return val <= math.MaxUint8
}
}

// indexFirstV4Label returns the index at which the reversed IPv4 address starts
// within domain, assuming domain itself is a valid ARPA IPv4 domain name.
// within domain. domain must be a valid ARPA IPv4 domain name. idx is never
// negative.
func indexFirstV4Label(domain string) (idx int) {
idx = len(domain) - len(arpaV4Suffix) + 1
for labelsNum := 0; labelsNum < net.IPv4len && idx > 0; labelsNum++ {
curIdx := strings.LastIndexByte(domain[:idx-1], '.') + 1
_, parseErr := strconv.ParseUint(domain[curIdx:idx-1], 10, 8)
if parseErr != nil {
return idx
if !isV4ARPALabel(domain[curIdx : idx-1]) {
break
}

idx = curIdx
Expand All @@ -378,13 +425,14 @@ func indexFirstV4Label(domain string) (idx int) {
}

// indexFirstV6Label returns the index at which the reversed IPv6 address starts
// within domain, assuming domain itself is a valid ARPA IPv6 domain name.
// within domain. domain must be a valid ARPA IPv6 domain name. idx is never
// negative.
func indexFirstV6Label(domain string) (idx int) {
idx = len(domain) - len(arpaV6Suffix) + 1
for labelsNum := 0; labelsNum < net.IPv6len*2 && idx > 0; labelsNum++ {
curIdx := idx - len("a.")
if curIdx > 1 && domain[curIdx-1] != '.' || fromHexByte(domain[curIdx]) == 0xff {
return idx
break
}

idx = curIdx
Expand All @@ -395,8 +443,10 @@ func indexFirstV6Label(domain string) (idx int) {

// ExtractReversedAddr searches for an ARPA subdomain within domain and returns
// the encoded prefix. It returns [ErrNotAReversedSubnet] if domain contains no
// valid ARPA subdomain. domain can be a domain name or an FQDN. The returned
// error will have the underlying type of [*AddrError].
// valid ARPA subdomain. The root ARPA domains "in-addr.arpa" and "ip6.arpa"
// even within a longer domain name are considered valid and turn into the zero
// prefixes of the corresponding family. domain can be a domain name or an
// FQDN. The returned error will have the underlying type of [*AddrError].
func ExtractReversedAddr(domain string) (pref netip.Prefix, err error) {
domain = strings.TrimSuffix(domain, ".")
err = ValidateDomainName(domain)
Expand All @@ -407,27 +457,30 @@ func ExtractReversedAddr(domain string) (pref netip.Prefix, err error) {
return netip.Prefix{}, err
}

defer makeAddrError(&err, domain, AddrKindARPA)

domain = strings.ToLower(domain)
// Wrap the deferred function to capture the actual value of domain.
defer func() { makeAddrError(&err, domain, AddrKindARPA) }()

var parseSubnet func(arpa string) (pref netip.Prefix, err error)
var indexFirstLabel func(arpa string) (idx int)
var sufLen int
switch {
case strings.HasSuffix(domain, arpaV4Suffix):
idx := indexFirstV4Label(domain)
case strings.HasSuffix(domain, arpaV4Suffix[len("."):]):
parseSubnet = subnetFromReversedV4
indexFirstLabel = indexFirstV4Label
sufLen = len(arpaV4Suffix)
case strings.HasSuffix(domain, arpaV6Suffix[len("."):]):
parseSubnet = subnetFromReversedV6
indexFirstLabel = indexFirstV6Label
sufLen = len(arpaV6Suffix)
default:
return netip.Prefix{}, ErrNotAReversedSubnet
}

domain = domain[idx:]
if len(domain) > len(arpaV4Suffix) {
return subnetFromReversedV4(domain)
}
case strings.HasSuffix(domain, arpaV6Suffix):
idx := indexFirstV6Label(domain)
if domLen := len(domain); domLen <= sufLen || domain[domLen-sufLen] == '.' {
arpa := domain[indexFirstLabel(domain):]

domain = domain[idx:]
if len(domain) > len(arpaV6Suffix) {
return subnetFromReversedV6(domain)
}
default:
// Go on.
return parseSubnet(arpa)
}

return netip.Prefix{}, ErrNotAReversedSubnet
Expand Down
Loading

0 comments on commit 1281448

Please sign in to comment.