diff --git a/go.mod b/go.mod index b8b0387..7cb2a92 100644 --- a/go.mod +++ b/go.mod @@ -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 ) diff --git a/go.sum b/go.sum index bf76007..b40ad7f 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/netutil/addr.go b/netutil/addr.go index 85958f2..1442724 100644 --- a/netutil/addr.go +++ b/netutil/addr.go @@ -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, @@ -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 diff --git a/netutil/addrconv.go b/netutil/addrconv.go new file mode 100644 index 0000000..f57da0e --- /dev/null +++ b/netutil/addrconv.go @@ -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) +} diff --git a/netutil/addrconv_example_test.go b/netutil/addrconv_example_test.go new file mode 100644 index 0000000..7e65e81 --- /dev/null +++ b/netutil/addrconv_example_test.go @@ -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: + // "::ffff:1.2.3.4", error: + // "invalid IP", error: bad ipv4 net.IP 1234::5678 + // "1234::5678", error: +} + +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: + // "1.2.3.4", error: + // "1234::5678", error: +} + +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: + // "::ffff:1.2.3.0/24", error: + // "invalid Prefix", error: bad ip for subnet 1234::/72: bad ipv4 net.IP 1234:: + // "1234::/72", error: +} + +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: + // "1.2.3.0/24", error: + // "1234::/72", error: +} diff --git a/netutil/addrfam.go b/netutil/addrfam.go new file mode 100644 index 0000000..45bb229 --- /dev/null +++ b/netutil/addrfam.go @@ -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 + } +} diff --git a/netutil/addrfam_example_test.go b/netutil/addrfam_example_test.go new file mode 100644 index 0000000..1abe3e7 --- /dev/null +++ b/netutil/addrfam_example_test.go @@ -0,0 +1,40 @@ +package netutil_test + +import ( + "fmt" + + "github.com/AdguardTeam/golibs/netutil" +) + +func ExampleAddrFamily_String() { + fmt.Println(netutil.AddrFamilyIPv4, netutil.AddrFamilyIPv6) + + // An empty family. + var fam netutil.AddrFamily + fmt.Println(fam) + + // An unsupported family. + fam = netutil.AddrFamily(1234) + fmt.Println(fam) + + // Output: + // ipv4 ipv6 + // none + // !bad_addr_fam_1234 +} + +func ExampleAddrFamilyFromRRType() { + // DNS type A. + fmt.Println(netutil.AddrFamilyFromRRType(1)) + + // DNS type AAAA. + fmt.Println(netutil.AddrFamilyFromRRType(28)) + + // Other DNS type. + fmt.Println(netutil.AddrFamilyFromRRType(1234)) + + // Output: + // ipv4 + // ipv6 + // none +} diff --git a/netutil/ip.go b/netutil/ip.go index ee46ae7..610fe70 100644 --- a/netutil/ip.go +++ b/netutil/ip.go @@ -4,6 +4,8 @@ import ( "fmt" "net" "strings" + + "golang.org/x/exp/slices" ) // IP Address Constants And Utilities @@ -16,12 +18,10 @@ const ( // CloneIP returns a clone of an IP address that doesn't share the same // underlying array with it. +// +// Deprecated: use slices.Clone. func CloneIP(ip net.IP) (clone net.IP) { - if ip != nil && len(ip) == 0 { - return net.IP{} - } - - return append(clone, ip...) + return slices.Clone(ip) } // CloneIPs returns a deep clone of ips. @@ -32,7 +32,7 @@ func CloneIPs(ips []net.IP) (clone []net.IP) { clone = make([]net.IP, len(ips)) for i, ip := range ips { - clone[i] = CloneIP(ip) + clone[i] = slices.Clone(ip) } return clone @@ -121,9 +121,9 @@ func CloneIPNet(n *net.IPNet) (clone *net.IPNet) { } return &net.IPNet{ - IP: CloneIP(n.IP), + IP: slices.Clone(n.IP), // TODO(e.burkov): Consider adding CloneIPMask. - Mask: net.IPMask(CloneIP(net.IP(n.Mask))), + Mask: net.IPMask(slices.Clone(net.IP(n.Mask))), } } diff --git a/netutil/ipmap_test.go b/netutil/ipmap_test.go index 7dacd6b..6ad07ed 100644 --- a/netutil/ipmap_test.go +++ b/netutil/ipmap_test.go @@ -40,12 +40,12 @@ func TestIPMap_allocs(t *testing.T) { }) t.Run("clear", func(t *testing.T) { - m := netutil.NewIPMap(1) - m.Set(testIPv4, struct{}{}) + clearMap := netutil.NewIPMap(1) + clearMap.Set(testIPv4, struct{}{}) allocs := testing.AllocsPerRun(100, func() { - m.Clear() - m.Set(testIPv4, struct{}{}) + clearMap.Clear() + clearMap.Set(testIPv4, struct{}{}) }) assert.Equal(t, float64(0), allocs) diff --git a/netutil/ipport.go b/netutil/ipport.go index e124134..eacfba5 100644 --- a/netutil/ipport.go +++ b/netutil/ipport.go @@ -1,11 +1,17 @@ package netutil -import "net" +import ( + "net" + + "golang.org/x/exp/slices" +) // IPPort And Utilities // IPPort is a convenient type for network addresses that contain an IP address // and a port, like "1.2.3.4:56789" or "[1234::cdef]:12345". +// +// Deprecated: use netip.AddrPort. type IPPort struct { IP net.IP Port int @@ -20,7 +26,7 @@ func IPPortFromAddr(a net.Addr) (ipp *IPPort) { } return &IPPort{ - IP: CloneIP(ip), + IP: slices.Clone(ip), Port: port, } } @@ -70,7 +76,7 @@ func (ipp *IPPort) Clone() (clone *IPPort) { } return &IPPort{ - IP: CloneIP(ipp.IP), + IP: slices.Clone(ipp.IP), Port: ipp.Port, } } @@ -93,7 +99,7 @@ func (ipp IPPort) String() (s string) { // TCP returns a *net.TCPAddr with a clone of ipp's IP address and its port. func (ipp *IPPort) TCP() (a *net.TCPAddr) { return &net.TCPAddr{ - IP: CloneIP(ipp.IP), + IP: slices.Clone(ipp.IP), Port: ipp.Port, } } @@ -101,7 +107,7 @@ func (ipp *IPPort) TCP() (a *net.TCPAddr) { // UDP returns a *net.UDPAddr with a clone of ipp's IP address and its port. func (ipp *IPPort) UDP() (a *net.UDPAddr) { return &net.UDPAddr{ - IP: CloneIP(ipp.IP), + IP: slices.Clone(ipp.IP), Port: ipp.Port, } }