]> Cypherpunks repositories - gostls13.git/commitdiff
net: precompute rfc6724policyTable in addrselect
authorTomasz Jezierski <tjomek.jezierski@gmail.com>
Sun, 24 Jul 2022 21:02:53 +0000 (23:02 +0200)
committerDaniel Martí <mvdan@mvdan.cc>
Mon, 5 Sep 2022 07:16:00 +0000 (07:16 +0000)
As net package has one of the biggest init time in standard library, I have tried to improve performance by doing two things in net/addrselect.go:
1. Precompute slice with RFC rules. Currently the rules are computed and sorted in init() function. We could save the time and allocations by using prepopulated values in sorted manner. The rules haven't changed since 2015. To be extra safe we could move order validation as test case. It should slightly speed up startup of each binary with "net" package and go dns resolver. It also saves 38 allocations, ~50% of allocations in init phase of `net` module.
2. Replace internal net.IP usage with netip.Addr in `sortByRFC6724` function. It results in ~40% performance improvement on samples from tests.

The only risk is the difference between net.IP and netip.Addr behaviour.

Init benchmark:
Init-8               1.89µs ± 2%    0.12µs ± 3%  -93.79%  (p=0.000 n=5+5)

name               old alloc/op   new alloc/op   delta
Init-8               1.05kB ± 0%    0.38kB ± 0%     ~     (zero variance)

name               old allocs/op  new allocs/op  delta
Init-8                 39.0 ± 0%       1.0 ± 0%     ~     (zero variance)

Whole sortByRFC6724 function benchmark:
name               old time/op    new time/op    delta
SortByRFC6724/0-8     463ns ± 3%     303ns ± 4%  -34.72%  (p=0.000 n=5+5)
SortByRFC6724/1-8     481ns ± 8%     306ns ± 1%  -36.46%  (p=0.000 n=5+5)
SortByRFC6724/2-8     470ns ± 4%     307ns ± 4%  -34.77%  (p=0.000 n=5+5)
SortByRFC6724/3-8     567ns ± 3%     367ns ± 3%  -35.28%  (p=0.000 n=5+5)
SortByRFC6724/4-8     918ns ± 3%     560ns ± 2%  -38.93%  (p=0.000 n=5+5)

Updates #54032

Change-Id: Ic18df1ea73805cb184c6ceb73470ca7f0b922032
Reviewed-on: https://go-review.googlesource.com/c/go/+/419356
Reviewed-by: Heschi Kreinick <heschi@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Damien Neil <dneil@google.com>
Run-TryBot: Damien Neil <dneil@google.com>
Reviewed-by: Daniel Martí <mvdan@mvdan.cc>
src/net/addrselect.go
src/net/addrselect_test.go

index 59380b94868faab05e75e6787786e2d1714b1bf2..b76183a34c42fe5b4ace7b6e3d260275dd6d293c 100644 (file)
@@ -6,7 +6,10 @@
 
 package net
 
-import "sort"
+import (
+       "net/netip"
+       "sort"
+)
 
 func sortByRFC6724(addrs []IPAddr) {
        if len(addrs) < 2 {
@@ -15,14 +18,15 @@ func sortByRFC6724(addrs []IPAddr) {
        sortByRFC6724withSrcs(addrs, srcAddrs(addrs))
 }
 
-func sortByRFC6724withSrcs(addrs []IPAddr, srcs []IP) {
+func sortByRFC6724withSrcs(addrs []IPAddr, srcs []netip.Addr) {
        if len(addrs) != len(srcs) {
                panic("internal error")
        }
        addrAttr := make([]ipAttr, len(addrs))
        srcAttr := make([]ipAttr, len(srcs))
        for i, v := range addrs {
-               addrAttr[i] = ipAttrOf(v.IP)
+               addrAttrIP, _ := netip.AddrFromSlice(v.IP)
+               addrAttr[i] = ipAttrOf(addrAttrIP)
                srcAttr[i] = ipAttrOf(srcs[i])
        }
        sort.Stable(&byRFC6724{
@@ -36,8 +40,8 @@ func sortByRFC6724withSrcs(addrs []IPAddr, srcs []IP) {
 // srcsAddrs tries to UDP-connect to each address to see if it has a
 // route. (This doesn't send any packets). The destination port
 // number is irrelevant.
-func srcAddrs(addrs []IPAddr) []IP {
-       srcs := make([]IP, len(addrs))
+func srcAddrs(addrs []IPAddr) []netip.Addr {
+       srcs := make([]netip.Addr, len(addrs))
        dst := UDPAddr{Port: 9}
        for i := range addrs {
                dst.IP = addrs[i].IP
@@ -45,7 +49,7 @@ func srcAddrs(addrs []IPAddr) []IP {
                c, err := DialUDP("udp", nil, &dst)
                if err == nil {
                        if src, ok := c.LocalAddr().(*UDPAddr); ok {
-                               srcs[i] = src.IP
+                               srcs[i], _ = netip.AddrFromSlice(src.IP)
                        }
                        c.Close()
                }
@@ -59,8 +63,8 @@ type ipAttr struct {
        Label      uint8
 }
 
-func ipAttrOf(ip IP) ipAttr {
-       if ip == nil {
+func ipAttrOf(ip netip.Addr) ipAttr {
+       if !ip.IsValid() {
                return ipAttr{}
        }
        match := rfc6724policyTable.Classify(ip)
@@ -74,7 +78,7 @@ func ipAttrOf(ip IP) ipAttr {
 type byRFC6724 struct {
        addrs    []IPAddr // addrs to sort
        addrAttr []ipAttr
-       srcs     []IP // or nil if unreachable
+       srcs     []netip.Addr // or not valid addr if unreachable
        srcAttr  []ipAttr
 }
 
@@ -108,13 +112,13 @@ func (s *byRFC6724) Less(i, j int) bool {
        // If DB is known to be unreachable or if Source(DB) is undefined, then
        // prefer DA.  Similarly, if DA is known to be unreachable or if
        // Source(DA) is undefined, then prefer DB.
-       if SourceDA == nil && SourceDB == nil {
+       if !SourceDA.IsValid() && !SourceDB.IsValid() {
                return false // "equal"
        }
-       if SourceDB == nil {
+       if !SourceDB.IsValid() {
                return preferDA
        }
-       if SourceDA == nil {
+       if !SourceDA.IsValid() {
                return preferDB
        }
 
@@ -184,7 +188,7 @@ func (s *byRFC6724) Less(i, j int) bool {
                return preferDB
        }
 
-       // Rule 9: Use longest matching prefix.
+       // Rule 9: Use the longest matching prefix.
        // When DA and DB belong to the same address family (both are IPv6 or
        // both are IPv4 [but see below]): If CommonPrefixLen(Source(DA), DA) >
        // CommonPrefixLen(Source(DB), DB), then prefer DA.  Similarly, if
@@ -212,7 +216,7 @@ func (s *byRFC6724) Less(i, j int) bool {
 }
 
 type policyTableEntry struct {
-       Prefix     *IPNet
+       Prefix     netip.Prefix
        Precedence uint8
        Label      uint8
 }
@@ -220,90 +224,75 @@ type policyTableEntry struct {
 type policyTable []policyTableEntry
 
 // RFC 6724 section 2.1.
+// Items are sorted by the size of their Prefix.Mask.Size,
 var rfc6724policyTable = policyTable{
        {
-               Prefix:     mustCIDR("::1/128"),
+               // "::1/128"
+               Prefix:     netip.PrefixFrom(netip.AddrFrom16([16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01}), 128),
                Precedence: 50,
                Label:      0,
        },
        {
-               Prefix:     mustCIDR("::/0"),
-               Precedence: 40,
-               Label:      1,
-       },
-       {
+               // "::ffff:0:0/96"
                // IPv4-compatible, etc.
-               Prefix:     mustCIDR("::ffff:0:0/96"),
+               Prefix:     netip.PrefixFrom(netip.AddrFrom16([16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff}), 96),
                Precedence: 35,
                Label:      4,
        },
        {
-               // 6to4
-               Prefix:     mustCIDR("2002::/16"),
-               Precedence: 30,
-               Label:      2,
+               // "::/96"
+               Prefix:     netip.PrefixFrom(netip.AddrFrom16([16]byte{}), 96),
+               Precedence: 1,
+               Label:      3,
        },
        {
+               // "2001::/32"
                // Teredo
-               Prefix:     mustCIDR("2001::/32"),
+               Prefix:     netip.PrefixFrom(netip.AddrFrom16([16]byte{0x20, 0x01}), 32),
                Precedence: 5,
                Label:      5,
        },
        {
-               Prefix:     mustCIDR("fc00::/7"),
-               Precedence: 3,
-               Label:      13,
+               // "2002::/16"
+               // 6to4
+               Prefix:     netip.PrefixFrom(netip.AddrFrom16([16]byte{0x20, 0x02}), 16),
+               Precedence: 30,
+               Label:      2,
        },
        {
-               Prefix:     mustCIDR("::/96"),
+               // "3ffe::/16"
+               Prefix:     netip.PrefixFrom(netip.AddrFrom16([16]byte{0x3f, 0xfe}), 16),
                Precedence: 1,
-               Label:      3,
+               Label:      12,
        },
        {
-               Prefix:     mustCIDR("fec0::/10"),
+               // "fec0::/10"
+               Prefix:     netip.PrefixFrom(netip.AddrFrom16([16]byte{0xfe, 0xc0}), 10),
                Precedence: 1,
                Label:      11,
        },
        {
-               Prefix:     mustCIDR("3ffe::/16"),
-               Precedence: 1,
-               Label:      12,
+               // "fc00::/7"
+               Prefix:     netip.PrefixFrom(netip.AddrFrom16([16]byte{0xfc}), 7),
+               Precedence: 3,
+               Label:      13,
+       },
+       {
+               // "::/0"
+               Prefix:     netip.PrefixFrom(netip.AddrFrom16([16]byte{}), 0),
+               Precedence: 40,
+               Label:      1,
        },
-}
-
-func init() {
-       sort.Sort(sort.Reverse(byMaskLength(rfc6724policyTable)))
-}
-
-// byMaskLength sorts policyTableEntry by the size of their Prefix.Mask.Size,
-// from smallest mask, to largest.
-type byMaskLength []policyTableEntry
-
-func (s byMaskLength) Len() int      { return len(s) }
-func (s byMaskLength) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
-func (s byMaskLength) Less(i, j int) bool {
-       isize, _ := s[i].Prefix.Mask.Size()
-       jsize, _ := s[j].Prefix.Mask.Size()
-       return isize < jsize
-}
-
-// mustCIDR calls ParseCIDR and panics on any error, or if the network
-// is not IPv6.
-func mustCIDR(s string) *IPNet {
-       ip, ipNet, err := ParseCIDR(s)
-       if err != nil {
-               panic(err.Error())
-       }
-       if len(ip) != IPv6len {
-               panic("unexpected IP length")
-       }
-       return ipNet
 }
 
 // Classify returns the policyTableEntry of the entry with the longest
 // matching prefix that contains ip.
 // The table t must be sorted from largest mask size to smallest.
-func (t policyTable) Classify(ip IP) policyTableEntry {
+func (t policyTable) Classify(ip netip.Addr) policyTableEntry {
+       // Prefix.Contains() will not match an IPv6 prefix for an IPv4 address.
+       if ip.Is4() {
+               ip = netip.AddrFrom16(ip.As16())
+       }
        for _, ent := range t {
                if ent.Prefix.Contains(ip) {
                        return ent
@@ -324,17 +313,18 @@ const (
        scopeGlobal         scope = 0xe
 )
 
-func classifyScope(ip IP) scope {
+func classifyScope(ip netip.Addr) scope {
        if ip.IsLoopback() || ip.IsLinkLocalUnicast() {
                return scopeLinkLocal
        }
-       ipv6 := len(ip) == IPv6len && ip.To4() == nil
+       ipv6 := ip.Is6() && !ip.Is4In6()
+       ipv6AsBytes := ip.As16()
        if ipv6 && ip.IsMulticast() {
-               return scope(ip[1] & 0xf)
+               return scope(ipv6AsBytes[1] & 0xf)
        }
        // Site-local addresses are defined in RFC 3513 section 2.5.6
        // (and deprecated in RFC 3879).
-       if ipv6 && ip[0] == 0xfe && ip[1]&0xc0 == 0xc0 {
+       if ipv6 && ipv6AsBytes[0] == 0xfe && ipv6AsBytes[1]&0xc0 == 0xc0 {
                return scopeSiteLocal
        }
        return scopeGlobal
@@ -350,30 +340,28 @@ func classifyScope(ip IP) scope {
 // If a and b are different IP versions, 0 is returned.
 //
 // See https://tools.ietf.org/html/rfc6724#section-2.2
-func commonPrefixLen(a, b IP) (cpl int) {
-       if a4 := a.To4(); a4 != nil {
-               a = a4
-       }
+func commonPrefixLen(a netip.Addr, b IP) (cpl int) {
        if b4 := b.To4(); b4 != nil {
                b = b4
        }
-       if len(a) != len(b) {
+       aAsSlice := a.AsSlice()
+       if len(aAsSlice) != len(b) {
                return 0
        }
        // If IPv6, only up to the prefix (first 64 bits)
-       if len(a) > 8 {
-               a = a[:8]
+       if len(aAsSlice) > 8 {
+               aAsSlice = aAsSlice[:8]
                b = b[:8]
        }
-       for len(a) > 0 {
-               if a[0] == b[0] {
+       for len(aAsSlice) > 0 {
+               if aAsSlice[0] == b[0] {
                        cpl += 8
-                       a = a[1:]
+                       aAsSlice = aAsSlice[1:]
                        b = b[1:]
                        continue
                }
                bits := 8
-               ab, bb := a[0], b[0]
+               ab, bb := aAsSlice[0], b[0]
                for {
                        ab >>= 1
                        bb >>= 1
index 2894d5d846ab99fe3997b64d30aabda2d66bfcd5..7e8134d754447b2d97eef3b8e4e979a5f1a729d1 100644 (file)
@@ -7,6 +7,7 @@
 package net
 
 import (
+       "net/netip"
        "reflect"
        "testing"
 )
@@ -14,7 +15,7 @@ import (
 func TestSortByRFC6724(t *testing.T) {
        tests := []struct {
                in      []IPAddr
-               srcs    []IP
+               srcs    []netip.Addr
                want    []IPAddr
                reverse bool // also test it starting backwards
        }{
@@ -26,9 +27,9 @@ func TestSortByRFC6724(t *testing.T) {
                                {IP: ParseIP("2001:db8:1::1")},
                                {IP: ParseIP("198.51.100.121")},
                        },
-                       srcs: []IP{
-                               ParseIP("2001:db8:1::2"),
-                               ParseIP("169.254.13.78"),
+                       srcs: []netip.Addr{
+                               netip.MustParseAddr("2001:db8:1::2"),
+                               netip.MustParseAddr("169.254.13.78"),
                        },
                        want: []IPAddr{
                                {IP: ParseIP("2001:db8:1::1")},
@@ -43,9 +44,9 @@ func TestSortByRFC6724(t *testing.T) {
                                {IP: ParseIP("2001:db8:1::1")},
                                {IP: ParseIP("198.51.100.121")},
                        },
-                       srcs: []IP{
-                               ParseIP("fe80::1"),
-                               ParseIP("198.51.100.117"),
+                       srcs: []netip.Addr{
+                               netip.MustParseAddr("fe80::1"),
+                               netip.MustParseAddr("198.51.100.117"),
                        },
                        want: []IPAddr{
                                {IP: ParseIP("198.51.100.121")},
@@ -60,9 +61,9 @@ func TestSortByRFC6724(t *testing.T) {
                                {IP: ParseIP("2001:db8:1::1")},
                                {IP: ParseIP("10.1.2.3")},
                        },
-                       srcs: []IP{
-                               ParseIP("2001:db8:1::2"),
-                               ParseIP("10.1.2.4"),
+                       srcs: []netip.Addr{
+                               netip.MustParseAddr("2001:db8:1::2"),
+                               netip.MustParseAddr("10.1.2.4"),
                        },
                        want: []IPAddr{
                                {IP: ParseIP("2001:db8:1::1")},
@@ -77,9 +78,9 @@ func TestSortByRFC6724(t *testing.T) {
                                {IP: ParseIP("2001:db8:1::1")},
                                {IP: ParseIP("fe80::1")},
                        },
-                       srcs: []IP{
-                               ParseIP("2001:db8:1::2"),
-                               ParseIP("fe80::2"),
+                       srcs: []netip.Addr{
+                               netip.MustParseAddr("2001:db8:1::2"),
+                               netip.MustParseAddr("fe80::2"),
                        },
                        want: []IPAddr{
                                {IP: ParseIP("fe80::1")},
@@ -99,13 +100,13 @@ func TestSortByRFC6724(t *testing.T) {
                                {IP: ParseIP("23.23.134.56")},
                                {IP: ParseIP("23.21.50.150")},
                        },
-                       srcs: []IP{
-                               ParseIP("10.2.3.4"),
-                               ParseIP("10.2.3.4"),
-                               ParseIP("10.2.3.4"),
-                               ParseIP("10.2.3.4"),
-                               ParseIP("10.2.3.4"),
-                               ParseIP("10.2.3.4"),
+                       srcs: []netip.Addr{
+                               netip.MustParseAddr("10.2.3.4"),
+                               netip.MustParseAddr("10.2.3.4"),
+                               netip.MustParseAddr("10.2.3.4"),
+                               netip.MustParseAddr("10.2.3.4"),
+                               netip.MustParseAddr("10.2.3.4"),
+                               netip.MustParseAddr("10.2.3.4"),
                        },
                        want: []IPAddr{
                                {IP: ParseIP("54.83.193.112")},
@@ -121,7 +122,7 @@ func TestSortByRFC6724(t *testing.T) {
        for i, tt := range tests {
                inCopy := make([]IPAddr, len(tt.in))
                copy(inCopy, tt.in)
-               srcCopy := make([]IP, len(tt.in))
+               srcCopy := make([]netip.Addr, len(tt.in))
                copy(srcCopy, tt.srcs)
                sortByRFC6724withSrcs(inCopy, srcCopy)
                if !reflect.DeepEqual(inCopy, tt.want) {
@@ -145,39 +146,100 @@ func TestSortByRFC6724(t *testing.T) {
 
 }
 
+func TestRFC6724PolicyTableOrder(t *testing.T) {
+       for i := 0; i < len(rfc6724policyTable)-1; i++ {
+               if !(rfc6724policyTable[i].Prefix.Bits() >= rfc6724policyTable[i+1].Prefix.Bits()) {
+                       t.Errorf("rfc6724policyTable item number %d sorted in wrong order = %d bits, next item = %d bits;", i, rfc6724policyTable[i].Prefix.Bits(), rfc6724policyTable[i+1].Prefix.Bits())
+               }
+       }
+}
+
+func TestRFC6724PolicyTableContent(t *testing.T) {
+       expectedRfc6724policyTable := policyTable{
+               {
+                       Prefix:     netip.MustParsePrefix("::1/128"),
+                       Precedence: 50,
+                       Label:      0,
+               },
+               {
+                       Prefix:     netip.MustParsePrefix("::ffff:0:0/96"),
+                       Precedence: 35,
+                       Label:      4,
+               },
+               {
+                       Prefix:     netip.MustParsePrefix("::/96"),
+                       Precedence: 1,
+                       Label:      3,
+               },
+               {
+                       Prefix:     netip.MustParsePrefix("2001::/32"),
+                       Precedence: 5,
+                       Label:      5,
+               },
+               {
+                       Prefix:     netip.MustParsePrefix("2002::/16"),
+                       Precedence: 30,
+                       Label:      2,
+               },
+               {
+                       Prefix:     netip.MustParsePrefix("3ffe::/16"),
+                       Precedence: 1,
+                       Label:      12,
+               },
+               {
+                       Prefix:     netip.MustParsePrefix("fec0::/10"),
+                       Precedence: 1,
+                       Label:      11,
+               },
+               {
+                       Prefix:     netip.MustParsePrefix("fc00::/7"),
+                       Precedence: 3,
+                       Label:      13,
+               },
+               {
+                       Prefix:     netip.MustParsePrefix("::/0"),
+                       Precedence: 40,
+                       Label:      1,
+               },
+       }
+       if !reflect.DeepEqual(rfc6724policyTable, expectedRfc6724policyTable) {
+               t.Errorf("rfc6724policyTable has wrong contend = %v; want %v", rfc6724policyTable, expectedRfc6724policyTable)
+       }
+}
+
 func TestRFC6724PolicyTableClassify(t *testing.T) {
        tests := []struct {
-               ip   IP
+               ip   netip.Addr
                want policyTableEntry
        }{
                {
-                       ip: ParseIP("127.0.0.1"),
+                       ip: netip.MustParseAddr("127.0.0.1"),
                        want: policyTableEntry{
-                               Prefix:     &IPNet{IP: ParseIP("::ffff:0:0"), Mask: CIDRMask(96, 128)},
+                               Prefix:     netip.MustParsePrefix("::ffff:0:0/96"),
                                Precedence: 35,
                                Label:      4,
                        },
                },
                {
-                       ip: ParseIP("2601:645:8002:a500:986f:1db8:c836:bd65"),
+                       ip: netip.MustParseAddr("2601:645:8002:a500:986f:1db8:c836:bd65"),
                        want: policyTableEntry{
-                               Prefix:     &IPNet{IP: ParseIP("::"), Mask: CIDRMask(0, 128)},
+                               Prefix:     netip.MustParsePrefix("::/0"),
                                Precedence: 40,
                                Label:      1,
                        },
                },
                {
-                       ip: ParseIP("::1"),
+                       ip: netip.MustParseAddr("::1"),
                        want: policyTableEntry{
-                               Prefix:     &IPNet{IP: ParseIP("::1"), Mask: CIDRMask(128, 128)},
+                               Prefix:     netip.MustParsePrefix("::1/128"),
                                Precedence: 50,
                                Label:      0,
                        },
                },
                {
-                       ip: ParseIP("2002::ab12"),
+                       ip: netip.MustParseAddr("2002::ab12"),
                        want: policyTableEntry{
-                               Prefix:     &IPNet{IP: ParseIP("2002::"), Mask: CIDRMask(16, 128)},
+                               Prefix:     netip.MustParsePrefix("2002::/16"),
                                Precedence: 30,
                                Label:      2,
                        },
@@ -193,24 +255,24 @@ func TestRFC6724PolicyTableClassify(t *testing.T) {
 
 func TestRFC6724ClassifyScope(t *testing.T) {
        tests := []struct {
-               ip   IP
+               ip   netip.Addr
                want scope
        }{
-               {ParseIP("127.0.0.1"), scopeLinkLocal},   // rfc6724#section-3.2
-               {ParseIP("::1"), scopeLinkLocal},         // rfc4007#section-4
-               {ParseIP("169.254.1.2"), scopeLinkLocal}, // rfc6724#section-3.2
-               {ParseIP("fec0::1"), scopeSiteLocal},
-               {ParseIP("8.8.8.8"), scopeGlobal},
-
-               {ParseIP("ff02::"), scopeLinkLocal},  // IPv6 multicast
-               {ParseIP("ff05::"), scopeSiteLocal},  // IPv6 multicast
-               {ParseIP("ff04::"), scopeAdminLocal}, // IPv6 multicast
-               {ParseIP("ff0e::"), scopeGlobal},     // IPv6 multicast
-
-               {IPv4(0xe0, 0, 0, 0), scopeGlobal},       // IPv4 link-local multicast as 16 bytes
-               {IPv4(0xe0, 2, 2, 2), scopeGlobal},       // IPv4 global multicast as 16 bytes
-               {IPv4(0xe0, 0, 0, 0).To4(), scopeGlobal}, // IPv4 link-local multicast as 4 bytes
-               {IPv4(0xe0, 2, 2, 2).To4(), scopeGlobal}, // IPv4 global multicast as 4 bytes
+               {netip.MustParseAddr("127.0.0.1"), scopeLinkLocal},   // rfc6724#section-3.2
+               {netip.MustParseAddr("::1"), scopeLinkLocal},         // rfc4007#section-4
+               {netip.MustParseAddr("169.254.1.2"), scopeLinkLocal}, // rfc6724#section-3.2
+               {netip.MustParseAddr("fec0::1"), scopeSiteLocal},
+               {netip.MustParseAddr("8.8.8.8"), scopeGlobal},
+
+               {netip.MustParseAddr("ff02::"), scopeLinkLocal},  // IPv6 multicast
+               {netip.MustParseAddr("ff05::"), scopeSiteLocal},  // IPv6 multicast
+               {netip.MustParseAddr("ff04::"), scopeAdminLocal}, // IPv6 multicast
+               {netip.MustParseAddr("ff0e::"), scopeGlobal},     // IPv6 multicast
+
+               {netip.AddrFrom16([16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xe0, 0, 0, 0}), scopeGlobal}, // IPv4 link-local multicast as 16 bytes
+               {netip.AddrFrom16([16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xe0, 2, 2, 2}), scopeGlobal}, // IPv4 global multicast as 16 bytes
+               {netip.AddrFrom4([4]byte{0xe0, 0, 0, 0}), scopeGlobal},                                       // IPv4 link-local multicast as 4 bytes
+               {netip.AddrFrom4([4]byte{0xe0, 2, 2, 2}), scopeGlobal},                                       // IPv4 global multicast as 4 bytes
        }
        for i, tt := range tests {
                got := classifyScope(tt.ip)
@@ -222,22 +284,23 @@ func TestRFC6724ClassifyScope(t *testing.T) {
 
 func TestRFC6724CommonPrefixLength(t *testing.T) {
        tests := []struct {
-               a, b IP
+               a    netip.Addr
+               b    IP
                want int
        }{
-               {ParseIP("fe80::1"), ParseIP("fe80::2"), 64},
-               {ParseIP("fe81::1"), ParseIP("fe80::2"), 15},
-               {ParseIP("127.0.0.1"), ParseIP("fe80::1"), 0}, // diff size
-               {IPv4(1, 2, 3, 4), IP{1, 2, 3, 4}, 32},
-               {IP{1, 2, 255, 255}, IP{1, 2, 0, 0}, 16},
-               {IP{1, 2, 127, 255}, IP{1, 2, 0, 0}, 17},
-               {IP{1, 2, 63, 255}, IP{1, 2, 0, 0}, 18},
-               {IP{1, 2, 31, 255}, IP{1, 2, 0, 0}, 19},
-               {IP{1, 2, 15, 255}, IP{1, 2, 0, 0}, 20},
-               {IP{1, 2, 7, 255}, IP{1, 2, 0, 0}, 21},
-               {IP{1, 2, 3, 255}, IP{1, 2, 0, 0}, 22},
-               {IP{1, 2, 1, 255}, IP{1, 2, 0, 0}, 23},
-               {IP{1, 2, 0, 255}, IP{1, 2, 0, 0}, 24},
+               {netip.MustParseAddr("fe80::1"), ParseIP("fe80::2"), 64},
+               {netip.MustParseAddr("fe81::1"), ParseIP("fe80::2"), 15},
+               {netip.MustParseAddr("127.0.0.1"), ParseIP("fe80::1"), 0}, // diff size
+               {netip.AddrFrom4([4]byte{1, 2, 3, 4}), IP{1, 2, 3, 4}, 32},
+               {netip.AddrFrom4([4]byte{1, 2, 255, 255}), IP{1, 2, 0, 0}, 16},
+               {netip.AddrFrom4([4]byte{1, 2, 127, 255}), IP{1, 2, 0, 0}, 17},
+               {netip.AddrFrom4([4]byte{1, 2, 63, 255}), IP{1, 2, 0, 0}, 18},
+               {netip.AddrFrom4([4]byte{1, 2, 31, 255}), IP{1, 2, 0, 0}, 19},
+               {netip.AddrFrom4([4]byte{1, 2, 15, 255}), IP{1, 2, 0, 0}, 20},
+               {netip.AddrFrom4([4]byte{1, 2, 7, 255}), IP{1, 2, 0, 0}, 21},
+               {netip.AddrFrom4([4]byte{1, 2, 3, 255}), IP{1, 2, 0, 0}, 22},
+               {netip.AddrFrom4([4]byte{1, 2, 1, 255}), IP{1, 2, 0, 0}, 23},
+               {netip.AddrFrom4([4]byte{1, 2, 0, 255}), IP{1, 2, 0, 0}, 24},
        }
        for i, tt := range tests {
                got := commonPrefixLen(tt.a, tt.b)