| | |
| | |
| | |
| |
|
| | |
| |
|
| | package net |
| |
|
| | import ( |
| | "net/netip" |
| | "slices" |
| | ) |
| |
|
| | func sortByRFC6724(addrs []IPAddr) { |
| | if len(addrs) < 2 { |
| | return |
| | } |
| | sortByRFC6724withSrcs(addrs, srcAddrs(addrs)) |
| | } |
| |
|
| | func sortByRFC6724withSrcs(addrs []IPAddr, srcs []netip.Addr) { |
| | if len(addrs) != len(srcs) { |
| | panic("internal error") |
| | } |
| | addrInfos := make([]byRFC6724Info, len(addrs)) |
| | for i, v := range addrs { |
| | addrAttrIP, _ := netip.AddrFromSlice(v.IP) |
| | addrInfos[i] = byRFC6724Info{ |
| | addr: addrs[i], |
| | addrAttr: ipAttrOf(addrAttrIP), |
| | src: srcs[i], |
| | srcAttr: ipAttrOf(srcs[i]), |
| | } |
| | } |
| | slices.SortStableFunc(addrInfos, compareByRFC6724) |
| | for i := range addrInfos { |
| | addrs[i] = addrInfos[i].addr |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | func srcAddrs(addrs []IPAddr) []netip.Addr { |
| | srcs := make([]netip.Addr, len(addrs)) |
| | dst := UDPAddr{Port: 53} |
| | 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], _ = netip.AddrFromSlice(src.IP) |
| | } |
| | c.Close() |
| | } |
| | } |
| | return srcs |
| | } |
| |
|
| | type ipAttr struct { |
| | Scope scope |
| | Precedence uint8 |
| | Label uint8 |
| | } |
| |
|
| | func ipAttrOf(ip netip.Addr) ipAttr { |
| | if !ip.IsValid() { |
| | return ipAttr{} |
| | } |
| | match := rfc6724policyTable.Classify(ip) |
| | return ipAttr{ |
| | Scope: classifyScope(ip), |
| | Precedence: match.Precedence, |
| | Label: match.Label, |
| | } |
| | } |
| |
|
| | type byRFC6724Info struct { |
| | addr IPAddr |
| | addrAttr ipAttr |
| | src netip.Addr |
| | srcAttr ipAttr |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | func compareByRFC6724(a, b byRFC6724Info) int { |
| | DA := a.addr.IP |
| | DB := b.addr.IP |
| | SourceDA := a.src |
| | SourceDB := b.src |
| | attrDA := &a.addrAttr |
| | attrDB := &b.addrAttr |
| | attrSourceDA := &a.srcAttr |
| | attrSourceDB := &b.srcAttr |
| |
|
| | const preferDA = -1 |
| | const preferDB = 1 |
| |
|
| | |
| | |
| | |
| | |
| | if !SourceDA.IsValid() && !SourceDB.IsValid() { |
| | return 0 |
| | } |
| | if !SourceDB.IsValid() { |
| | return preferDA |
| | } |
| | if !SourceDA.IsValid() { |
| | return preferDB |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | if attrDA.Scope == attrSourceDA.Scope && attrDB.Scope != attrSourceDB.Scope { |
| | return preferDA |
| | } |
| | if attrDA.Scope != attrSourceDA.Scope && attrDB.Scope == attrSourceDB.Scope { |
| | return preferDB |
| | } |
| |
|
| | |
| | |
| | |
| | |
| |
|
| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| |
|
| | |
| |
|
| | |
| | |
| | |
| | |
| | if attrSourceDA.Label == attrDA.Label && |
| | attrSourceDB.Label != attrDB.Label { |
| | return preferDA |
| | } |
| | if attrSourceDA.Label != attrDA.Label && |
| | attrSourceDB.Label == attrDB.Label { |
| | return preferDB |
| | } |
| |
|
| | |
| | |
| | |
| | if attrDA.Precedence > attrDB.Precedence { |
| | return preferDA |
| | } |
| | if attrDA.Precedence < attrDB.Precedence { |
| | return preferDB |
| | } |
| |
|
| | |
| | |
| | |
| | |
| |
|
| | |
| |
|
| | |
| | |
| | |
| | if attrDA.Scope < attrDB.Scope { |
| | return preferDA |
| | } |
| | if attrDA.Scope > attrDB.Scope { |
| | return preferDB |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | 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 |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | return 0 |
| | } |
| |
|
| | type policyTableEntry struct { |
| | Prefix netip.Prefix |
| | Precedence uint8 |
| | Label uint8 |
| | } |
| |
|
| | type policyTable []policyTableEntry |
| |
|
| | |
| | |
| | var rfc6724policyTable = policyTable{ |
| | { |
| | |
| | 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: netip.PrefixFrom(netip.AddrFrom16([16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff}), 96), |
| | Precedence: 35, |
| | Label: 4, |
| | }, |
| | { |
| | |
| | Prefix: netip.PrefixFrom(netip.AddrFrom16([16]byte{}), 96), |
| | Precedence: 1, |
| | Label: 3, |
| | }, |
| | { |
| | |
| | |
| | Prefix: netip.PrefixFrom(netip.AddrFrom16([16]byte{0x20, 0x01}), 32), |
| | Precedence: 5, |
| | Label: 5, |
| | }, |
| | { |
| | |
| | |
| | Prefix: netip.PrefixFrom(netip.AddrFrom16([16]byte{0x20, 0x02}), 16), |
| | Precedence: 30, |
| | Label: 2, |
| | }, |
| | { |
| | |
| | Prefix: netip.PrefixFrom(netip.AddrFrom16([16]byte{0x3f, 0xfe}), 16), |
| | Precedence: 1, |
| | Label: 12, |
| | }, |
| | { |
| | |
| | Prefix: netip.PrefixFrom(netip.AddrFrom16([16]byte{0xfe, 0xc0}), 10), |
| | Precedence: 1, |
| | Label: 11, |
| | }, |
| | { |
| | |
| | Prefix: netip.PrefixFrom(netip.AddrFrom16([16]byte{0xfc}), 7), |
| | Precedence: 3, |
| | Label: 13, |
| | }, |
| | { |
| | |
| | Prefix: netip.PrefixFrom(netip.AddrFrom16([16]byte{}), 0), |
| | Precedence: 40, |
| | Label: 1, |
| | }, |
| | } |
| |
|
| | |
| | |
| | |
| | func (t policyTable) Classify(ip netip.Addr) policyTableEntry { |
| | |
| | if ip.Is4() { |
| | ip = netip.AddrFrom16(ip.As16()) |
| | } |
| | for _, ent := range t { |
| | if ent.Prefix.Contains(ip) { |
| | return ent |
| | } |
| | } |
| | return policyTableEntry{} |
| | } |
| |
|
| | |
| | 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 netip.Addr) scope { |
| | if ip.IsLoopback() || ip.IsLinkLocalUnicast() { |
| | return scopeLinkLocal |
| | } |
| | ipv6 := ip.Is6() && !ip.Is4In6() |
| | ipv6AsBytes := ip.As16() |
| | if ipv6 && ip.IsMulticast() { |
| | return scope(ipv6AsBytes[1] & 0xf) |
| | } |
| | |
| | |
| | if ipv6 && ipv6AsBytes[0] == 0xfe && ipv6AsBytes[1]&0xc0 == 0xc0 { |
| | return scopeSiteLocal |
| | } |
| | return scopeGlobal |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | func commonPrefixLen(a netip.Addr, b IP) (cpl int) { |
| | if b4 := b.To4(); b4 != nil { |
| | b = b4 |
| | } |
| | aAsSlice := a.AsSlice() |
| | if len(aAsSlice) != len(b) { |
| | return 0 |
| | } |
| | |
| | if len(aAsSlice) > 8 { |
| | aAsSlice = aAsSlice[:8] |
| | b = b[:8] |
| | } |
| | for len(aAsSlice) > 0 { |
| | if aAsSlice[0] == b[0] { |
| | cpl += 8 |
| | aAsSlice = aAsSlice[1:] |
| | b = b[1:] |
| | continue |
| | } |
| | bits := 8 |
| | ab, bb := aAsSlice[0], b[0] |
| | for { |
| | ab >>= 1 |
| | bb >>= 1 |
| | bits-- |
| | if ab == bb { |
| | cpl += bits |
| | return |
| | } |
| | } |
| | } |
| | return |
| | } |
| |
|