package net
-import "sort"
+import (
+ "net/netip"
+ "sort"
+)
func sortByRFC6724(addrs []IPAddr) {
if len(addrs) < 2 {
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{
// 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
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()
}
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)
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
}
// 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
}
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
}
type policyTableEntry struct {
- Prefix *IPNet
+ Prefix netip.Prefix
Precedence uint8
Label uint8
}
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
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
// 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
package net
import (
+ "net/netip"
"reflect"
"testing"
)
func TestSortByRFC6724(t *testing.T) {
tests := []struct {
in []IPAddr
- srcs []IP
+ srcs []netip.Addr
want []IPAddr
reverse bool // also test it starting backwards
}{
{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")},
{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")},
{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")},
{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")},
{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")},
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) {
}
+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,
},
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)
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)