Skip to content

Commit

Permalink
netutil: add netip.Addr utils, depr old utils
Browse files Browse the repository at this point in the history
  • Loading branch information
ainar-g committed Oct 21, 2022
1 parent 8fa05cb commit 01a14de
Show file tree
Hide file tree
Showing 10 changed files with 374 additions and 39 deletions.
11 changes: 6 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
module github.com/AdguardTeam/golibs

go 1.17
go 1.18

require (
github.com/stretchr/testify v1.7.1
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4
github.com/stretchr/testify v1.8.0
golang.org/x/exp v0.0.0-20221019170559-20944726eadf
golang.org/x/net v0.1.0
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/text v0.3.7 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
golang.org/x/text v0.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
22 changes: 11 additions & 11 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,18 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
golang.org/x/exp v0.0.0-20221019170559-20944726eadf h1:nFVjjKDgNY37+ZSYCJmtYf7tOlfQswHqplG2eosjOMg=
golang.org/x/exp v0.0.0-20221019170559-20944726eadf/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
11 changes: 5 additions & 6 deletions netutil/addr.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,17 @@ import (
"strconv"
"strings"

"golang.org/x/exp/slices"
"golang.org/x/net/idna"
)

// Various Network Address Utilities

// CloneMAC returns a clone of a MAC address.
//
// Deprecated: use slices.Clone.
func CloneMAC(mac net.HardwareAddr) (clone net.HardwareAddr) {
if mac != nil && len(mac) == 0 {
return net.HardwareAddr{}
}

return append(clone, mac...)
return slices.Clone(mac)
}

// CloneURL returns a deep clone of u. The User pointer of clone is the same,
Expand Down Expand Up @@ -266,7 +265,7 @@ func ValidateServiceNameLabel(label string) (err error) {

// TODO(e.burkov): Validate adjacent hyphens since service labels can't be
// internationalized. See RFC 6336 Section 5.1.
if err := ValidateDomainNameLabel(label[1:]); err != nil {
if err = ValidateDomainNameLabel(label[1:]); err != nil {
err = errors.Unwrap(err)
if rerr, ok := err.(*RuneError); ok {
rerr.Kind = AddrKindSRVLabel
Expand Down
119 changes: 119 additions & 0 deletions netutil/addrconv.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package netutil

import (
"fmt"
"net"
"net/netip"
)

// IPv4Localhost returns 127.0.0.1, which returns true for [netip.Addr.Is4].
// IPv4Unspecified returns the IPv4 unspecified address "0.0.0.0".
func IPv4Localhost() (ip netip.Addr) { return netip.AddrFrom4([4]byte{127, 0, 0, 1}) }

// IPv6Localhost returns ::1, which returns true for [netip.Addr.Is6].
func IPv6Localhost() (ip netip.Addr) { return netip.AddrFrom16([16]byte{15: 1}) }

// ZeroPrefix returns an IP subnet with prefix 0 and all bytes of the IP address
// set to 0. fam must be either [AddrFamilyIPv4] or [AddrFamilyIPv6].
func ZeroPrefix(fam AddrFamily) (n netip.Prefix) {
switch fam {
case AddrFamilyIPv4:
return netip.PrefixFrom(netip.IPv4Unspecified(), 0)
case AddrFamilyIPv6:
return netip.PrefixFrom(netip.IPv6Unspecified(), 0)
default:
panic(badAddrFam("ZeroPrefix", fam))
}
}

// badAddrFam is a helper that returns an informative error for panics caused by
// bad address-family values.
func badAddrFam(fn string, fam AddrFamily) (err error) {
return fmt.Errorf("netutil.%s: bad address family %s", fn, fam)
}

// IPToAddr converts a [net.IP] into a [netip.Addr] of the given family and
// returns a meaningful error. fam must be either [AddrFamilyIPv4] or
// [AddrFamilyIPv6].
//
// See also [IPToAddrNoMapped].
func IPToAddr(ip net.IP, fam AddrFamily) (addr netip.Addr, err error) {
switch fam {
case AddrFamilyIPv4:
// Make sure that we use the IPv4 form of the address to make sure that
// netip.Addr doesn't turn out to be an IPv6 one when it really should
// be an IPv4 one.
ip4 := ip.To4()
if ip4 == nil {
return netip.Addr{}, fmt.Errorf("bad ipv4 net.IP %v", ip)
}

ip = ip4
case AddrFamilyIPv6:
// Again, make sure that we use the correct form according to the
// address family.
ip = ip.To16()
default:
panic(badAddrFam("IPToAddr", fam))
}

addr, ok := netip.AddrFromSlice(ip)
if !ok {
return netip.Addr{}, fmt.Errorf("bad net.IP value %v", ip)
}

return addr, nil
}

// IPToAddrNoMapped is like [IPToAddr] but it detects the address family
// automatically by assuming that every IPv6-mapped IPv4 address is actually an
// IPv4 address. Do not use IPToAddrNoMapped where this assumption isn't safe.
func IPToAddrNoMapped(ip net.IP) (addr netip.Addr, err error) {
if ip4 := ip.To4(); ip4 != nil {
return IPToAddr(ip4, AddrFamilyIPv4)
}

return IPToAddr(ip, AddrFamilyIPv6)
}

// IPNetToPrefix is a helper that converts a [*net.IPNet] into a [netip.Prefix].
// If subnet is nil, it returns netip.Prefix{}. fam must be either
// [AddrFamilyIPv4] or [AddrFamilyIPv6].
//
// See also [IPNetToPrefixNoMapped].
func IPNetToPrefix(subnet *net.IPNet, fam AddrFamily) (p netip.Prefix, err error) {
if subnet == nil {
return netip.Prefix{}, nil
}

addr, err := IPToAddr(subnet.IP, fam)
if err != nil {
return netip.Prefix{}, fmt.Errorf("bad ip for subnet %v: %w", subnet, err)
}

ones, _ := subnet.Mask.Size()
p = netip.PrefixFrom(addr, ones)
if !p.IsValid() {
return netip.Prefix{}, fmt.Errorf("bad subnet %v", subnet)
}

return p, nil
}

// IPNetToPrefixNoMapped is like [IPNetToPrefix] but it detects the address
// family automatically by assuming that every IPv6-mapped IPv4 address is
// actually an IPv4 address. Do not use IPNetToPrefixNoMapped where this
// assumption isn't safe.
func IPNetToPrefixNoMapped(subnet *net.IPNet) (p netip.Prefix, err error) {
if subnet == nil {
return netip.Prefix{}, nil
}

if ip4 := subnet.IP.To4(); ip4 != nil {
subnet.IP = ip4

return IPNetToPrefix(subnet, AddrFamilyIPv4)
}

return IPNetToPrefix(subnet, AddrFamilyIPv6)
}
116 changes: 116 additions & 0 deletions netutil/addrconv_example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package netutil_test

import (
"fmt"
"net"

"github.com/AdguardTeam/golibs/netutil"
)

func ExampleIPv4Localhost() {
fmt.Println(netutil.IPv4Localhost())

// Output:
// 127.0.0.1
}

func ExampleIPv6Localhost() {
fmt.Println(netutil.IPv6Localhost())

// Output:
// ::1
}

func ExampleZeroPrefix() {
fmt.Println(netutil.ZeroPrefix(netutil.AddrFamilyIPv4))
fmt.Println(netutil.ZeroPrefix(netutil.AddrFamilyIPv6))

// Output:
// 0.0.0.0/0
// ::/0
}

func ExampleIPToAddr() {
ip := net.ParseIP("1.2.3.4")
addr, err := netutil.IPToAddr(ip, netutil.AddrFamilyIPv4)
fmt.Printf("%q, error: %v\n", addr, err)

addr, err = netutil.IPToAddr(ip, netutil.AddrFamilyIPv6)
fmt.Printf("%q, error: %v\n", addr, err)

ip = net.ParseIP("1234::5678")
addr, err = netutil.IPToAddr(ip, netutil.AddrFamilyIPv4)
fmt.Printf("%q, error: %v\n", addr, err)

addr, err = netutil.IPToAddr(ip, netutil.AddrFamilyIPv6)
fmt.Printf("%q, error: %v\n", addr, err)

// Output:
// "1.2.3.4", error: <nil>
// "::ffff:1.2.3.4", error: <nil>
// "invalid IP", error: bad ipv4 net.IP 1234::5678
// "1234::5678", error: <nil>
}

func ExampleIPToAddrNoMapped() {
ip := net.ParseIP("1.2.3.4")
addr, err := netutil.IPToAddrNoMapped(ip)
fmt.Printf("%q, error: %v\n", addr, err)

ip = net.IP{1, 2, 3, 4}
addr, err = netutil.IPToAddrNoMapped(ip)
fmt.Printf("%q, error: %v\n", addr, err)

ip = net.ParseIP("1234::5678")
addr, err = netutil.IPToAddrNoMapped(ip)
fmt.Printf("%q, error: %v\n", addr, err)

// Output:
// "1.2.3.4", error: <nil>
// "1.2.3.4", error: <nil>
// "1234::5678", error: <nil>
}

func ExampleIPNetToPrefix() {
_, n, _ := net.ParseCIDR("1.2.3.0/24")
pref, err := netutil.IPNetToPrefix(n, netutil.AddrFamilyIPv4)
fmt.Printf("%q, error: %v\n", pref, err)

pref, err = netutil.IPNetToPrefix(n, netutil.AddrFamilyIPv6)
fmt.Printf("%q, error: %v\n", pref, err)

_, n, _ = net.ParseCIDR("1234::/72")
pref, err = netutil.IPNetToPrefix(n, netutil.AddrFamilyIPv4)
fmt.Printf("%q, error: %v\n", pref, err)

pref, err = netutil.IPNetToPrefix(n, netutil.AddrFamilyIPv6)
fmt.Printf("%q, error: %v\n", pref, err)

// Output:
// "1.2.3.0/24", error: <nil>
// "::ffff:1.2.3.0/24", error: <nil>
// "invalid Prefix", error: bad ip for subnet 1234::/72: bad ipv4 net.IP 1234::
// "1234::/72", error: <nil>
}

func ExampleIPNetToPrefixNoMapped() {
_, n, _ := net.ParseCIDR("1.2.3.0/24")
pref, err := netutil.IPNetToPrefixNoMapped(n)
fmt.Printf("%q, error: %v\n", pref, err)

n = &net.IPNet{
IP: net.IP{1, 2, 3, 0},
Mask: net.CIDRMask(24, 32),
}
pref, err = netutil.IPNetToPrefixNoMapped(n)
fmt.Printf("%q, error: %v\n", pref, err)

_, n, _ = net.ParseCIDR("1234::/72")
pref, err = netutil.IPNetToPrefixNoMapped(n)
fmt.Printf("%q, error: %v\n", pref, err)

// Output:
// "1.2.3.0/24", error: <nil>
// "1.2.3.0/24", error: <nil>
// "1234::/72", error: <nil>
}
54 changes: 54 additions & 0 deletions netutil/addrfam.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package netutil

import (
"fmt"
)

// AddrFamily is the type for IANA address family numbers.
type AddrFamily uint16

// An incomplete list of IANA address family numbers.
//
// See https://www.iana.org/assignments/address-family-numbers/address-family-numbers.xhtml.
const (
AddrFamilyNone AddrFamily = 0
AddrFamilyIPv4 AddrFamily = 1
AddrFamilyIPv6 AddrFamily = 2
)

// type check
var _ fmt.Stringer = AddrFamilyNone

// String implements the [fmt.Stringer] interface for AddrFamily.
func (f AddrFamily) String() (s string) {
switch f {
case AddrFamilyNone:
return "none"
case AddrFamilyIPv4:
return "ipv4"
case AddrFamilyIPv6:
return "ipv6"
default:
return fmt.Sprintf("!bad_addr_fam_%d", f)
}
}

// Constants to avoid a dependency on github.com/miekg/dns.
const (
dnsTypeA uint16 = 1
dnsTypeAAAA uint16 = 28
)

// AddrFamilyFromRRType returns an AddrFamily appropriate for the DNS resource
// record type rr. That is, [AddrFamilyIPv4] for DNS type A (1),
// [AddrFamilyIPv6] for DNS type AAAA (28), and [AddrFamilyNone] otherwise.
func AddrFamilyFromRRType(rr uint16) (fam AddrFamily) {
switch rr {
case dnsTypeA:
return AddrFamilyIPv4
case dnsTypeAAAA:
return AddrFamilyIPv6
default:
return AddrFamilyNone
}
}
Loading

0 comments on commit 01a14de

Please sign in to comment.