diff options
Diffstat (limited to 'src/net/addrselect.go')
-rw-r--r-- | src/net/addrselect.go | 390 |
1 files changed, 390 insertions, 0 deletions
diff --git a/src/net/addrselect.go b/src/net/addrselect.go new file mode 100644 index 0000000..7c0dfe2 --- /dev/null +++ b/src/net/addrselect.go @@ -0,0 +1,390 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris + +// Minimal RFC 6724 address selection. + +package net + +import "sort" + +func sortByRFC6724(addrs []IPAddr) { + if len(addrs) < 2 { + return + } + sortByRFC6724withSrcs(addrs, srcAddrs(addrs)) +} + +func sortByRFC6724withSrcs(addrs []IPAddr, srcs []IP) { + 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) + srcAttr[i] = ipAttrOf(srcs[i]) + } + sort.Stable(&byRFC6724{ + addrs: addrs, + addrAttr: addrAttr, + srcs: srcs, + srcAttr: srcAttr, + }) +} + +// 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)) + dst := UDPAddr{Port: 9} + for i := range addrs { + dst.IP = addrs[i].IP + dst.Zone = addrs[i].Zone + c, err := DialUDP("udp", nil, &dst) + if err == nil { + if src, ok := c.LocalAddr().(*UDPAddr); ok { + srcs[i] = src.IP + } + c.Close() + } + } + return srcs +} + +type ipAttr struct { + Scope scope + Precedence uint8 + Label uint8 +} + +func ipAttrOf(ip IP) ipAttr { + if ip == nil { + return ipAttr{} + } + match := rfc6724policyTable.Classify(ip) + return ipAttr{ + Scope: classifyScope(ip), + Precedence: match.Precedence, + Label: match.Label, + } +} + +type byRFC6724 struct { + addrs []IPAddr // addrs to sort + addrAttr []ipAttr + srcs []IP // or nil if unreachable + srcAttr []ipAttr +} + +func (s *byRFC6724) Len() int { return len(s.addrs) } + +func (s *byRFC6724) Swap(i, j int) { + s.addrs[i], s.addrs[j] = s.addrs[j], s.addrs[i] + s.srcs[i], s.srcs[j] = s.srcs[j], s.srcs[i] + s.addrAttr[i], s.addrAttr[j] = s.addrAttr[j], s.addrAttr[i] + s.srcAttr[i], s.srcAttr[j] = s.srcAttr[j], s.srcAttr[i] +} + +// Less reports whether i is a better destination address for this +// host than j. +// +// The algorithm and variable names comes from RFC 6724 section 6. +func (s *byRFC6724) Less(i, j int) bool { + DA := s.addrs[i].IP + DB := s.addrs[j].IP + SourceDA := s.srcs[i] + SourceDB := s.srcs[j] + attrDA := &s.addrAttr[i] + attrDB := &s.addrAttr[j] + attrSourceDA := &s.srcAttr[i] + attrSourceDB := &s.srcAttr[j] + + const preferDA = true + const preferDB = false + + // Rule 1: Avoid unusable destinations. + // 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 { + return false // "equal" + } + if SourceDB == nil { + return preferDA + } + if SourceDA == nil { + return preferDB + } + + // Rule 2: Prefer matching scope. + // If Scope(DA) = Scope(Source(DA)) and Scope(DB) <> Scope(Source(DB)), + // then prefer DA. Similarly, if Scope(DA) <> Scope(Source(DA)) and + // Scope(DB) = Scope(Source(DB)), then prefer DB. + if attrDA.Scope == attrSourceDA.Scope && attrDB.Scope != attrSourceDB.Scope { + return preferDA + } + if attrDA.Scope != attrSourceDA.Scope && attrDB.Scope == attrSourceDB.Scope { + return preferDB + } + + // Rule 3: Avoid deprecated addresses. + // If Source(DA) is deprecated and Source(DB) is not, then prefer DB. + // Similarly, if Source(DA) is not deprecated and Source(DB) is + // deprecated, then prefer DA. + + // TODO(bradfitz): implement? low priority for now. + + // Rule 4: Prefer home addresses. + // If Source(DA) is simultaneously a home address and care-of address + // and Source(DB) is not, then prefer DA. Similarly, if Source(DB) is + // simultaneously a home address and care-of address and Source(DA) is + // not, then prefer DB. + + // TODO(bradfitz): implement? low priority for now. + + // Rule 5: Prefer matching label. + // If Label(Source(DA)) = Label(DA) and Label(Source(DB)) <> Label(DB), + // then prefer DA. Similarly, if Label(Source(DA)) <> Label(DA) and + // Label(Source(DB)) = Label(DB), then prefer DB. + if attrSourceDA.Label == attrDA.Label && + attrSourceDB.Label != attrDB.Label { + return preferDA + } + if attrSourceDA.Label != attrDA.Label && + attrSourceDB.Label == attrDB.Label { + return preferDB + } + + // Rule 6: Prefer higher precedence. + // If Precedence(DA) > Precedence(DB), then prefer DA. Similarly, if + // Precedence(DA) < Precedence(DB), then prefer DB. + if attrDA.Precedence > attrDB.Precedence { + return preferDA + } + if attrDA.Precedence < attrDB.Precedence { + return preferDB + } + + // Rule 7: Prefer native transport. + // If DA is reached via an encapsulating transition mechanism (e.g., + // IPv6 in IPv4) and DB is not, then prefer DB. Similarly, if DB is + // reached via encapsulation and DA is not, then prefer DA. + + // TODO(bradfitz): implement? low priority for now. + + // Rule 8: Prefer smaller scope. + // If Scope(DA) < Scope(DB), then prefer DA. Similarly, if Scope(DA) > + // Scope(DB), then prefer DB. + if attrDA.Scope < attrDB.Scope { + return preferDA + } + if attrDA.Scope > attrDB.Scope { + return preferDB + } + + // Rule 9: Use 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 + // CommonPrefixLen(Source(DA), DA) < CommonPrefixLen(Source(DB), DB), + // then prefer DB. + // + // However, applying this rule to IPv4 addresses causes + // problems (see issues 13283 and 18518), so limit to IPv6. + if DA.To4() == nil && DB.To4() == nil { + commonA := commonPrefixLen(SourceDA, DA) + commonB := commonPrefixLen(SourceDB, DB) + + if commonA > commonB { + return preferDA + } + if commonA < commonB { + return preferDB + } + } + + // Rule 10: Otherwise, leave the order unchanged. + // If DA preceded DB in the original list, prefer DA. + // Otherwise, prefer DB. + return false // "equal" +} + +type policyTableEntry struct { + Prefix *IPNet + Precedence uint8 + Label uint8 +} + +type policyTable []policyTableEntry + +// RFC 6724 section 2.1. +var rfc6724policyTable = policyTable{ + { + Prefix: mustCIDR("::1/128"), + Precedence: 50, + Label: 0, + }, + { + Prefix: mustCIDR("::/0"), + Precedence: 40, + Label: 1, + }, + { + // IPv4-compatible, etc. + Prefix: mustCIDR("::ffff:0:0/96"), + Precedence: 35, + Label: 4, + }, + { + // 6to4 + Prefix: mustCIDR("2002::/16"), + Precedence: 30, + Label: 2, + }, + { + // Teredo + Prefix: mustCIDR("2001::/32"), + Precedence: 5, + Label: 5, + }, + { + Prefix: mustCIDR("fc00::/7"), + Precedence: 3, + Label: 13, + }, + { + Prefix: mustCIDR("::/96"), + Precedence: 1, + Label: 3, + }, + { + Prefix: mustCIDR("fec0::/10"), + Precedence: 1, + Label: 11, + }, + { + Prefix: mustCIDR("3ffe::/16"), + Precedence: 1, + Label: 12, + }, +} + +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 { + for _, ent := range t { + if ent.Prefix.Contains(ip) { + return ent + } + } + return policyTableEntry{} +} + +// RFC 6724 section 3.1. +type scope uint8 + +const ( + scopeInterfaceLocal scope = 0x1 + scopeLinkLocal scope = 0x2 + scopeAdminLocal scope = 0x4 + scopeSiteLocal scope = 0x5 + scopeOrgLocal scope = 0x8 + scopeGlobal scope = 0xe +) + +func classifyScope(ip IP) scope { + if ip.IsLoopback() || ip.IsLinkLocalUnicast() { + return scopeLinkLocal + } + ipv6 := len(ip) == IPv6len && ip.To4() == nil + if ipv6 && ip.IsMulticast() { + return scope(ip[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 { + return scopeSiteLocal + } + return scopeGlobal +} + +// commonPrefixLen reports the length of the longest prefix (looking +// at the most significant, or leftmost, bits) that the +// two addresses have in common, up to the length of a's prefix (i.e., +// the portion of the address not including the interface ID). +// +// If a or b is an IPv4 address as an IPv6 address, the IPv4 addresses +// are compared (with max common prefix length of 32). +// 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 + } + if b4 := b.To4(); b4 != nil { + b = b4 + } + if len(a) != len(b) { + return 0 + } + // If IPv6, only up to the prefix (first 64 bits) + if len(a) > 8 { + a = a[:8] + b = b[:8] + } + for len(a) > 0 { + if a[0] == b[0] { + cpl += 8 + a = a[1:] + b = b[1:] + continue + } + bits := 8 + ab, bb := a[0], b[0] + for { + ab >>= 1 + bb >>= 1 + bits-- + if ab == bb { + cpl += bits + return + } + } + } + return +} |