|
7 | 7 | package net |
8 | 8 |
|
9 | 9 | import ( |
| 10 | + "math/rand/v2" |
10 | 11 | "net/netip" |
11 | 12 | "slices" |
12 | 13 | ) |
13 | 14 |
|
14 | | -func sortByRFC6724(addrs []IPAddr) { |
| 15 | +// sortByRFC6724 sort ip by RFC6724 |
| 16 | +// dnsLoadBalance true for dial usage with shuffle before sort |
| 17 | +// dnsLoadBalance flase for simple DNS with stable sort |
| 18 | +func sortByRFC6724(addrs []IPAddr, dnsLoadbalance bool) { |
15 | 19 | if len(addrs) < 2 { |
16 | 20 | return |
17 | 21 | } |
18 | | - sortByRFC6724withSrcs(addrs, srcAddrs(addrs)) |
| 22 | + sortByRFC6724withSrcs(addrs, srcAddrs(addrs), dnsLoadbalance) |
19 | 23 | } |
20 | 24 |
|
21 | | -func sortByRFC6724withSrcs(addrs []IPAddr, srcs []netip.Addr) { |
| 25 | +// sortByRFC6724withSrcs sort by RFC6724 with srcs |
| 26 | +// shuffle addrInfos before sorting if dnsLoadbalance is true |
| 27 | +// section 6. Rule9 and Rule10 superseded for DNS load-balance indirectly |
| 28 | +func sortByRFC6724withSrcs(addrs []IPAddr, srcs []netip.Addr, dnsLoadbalance bool) { |
22 | 29 | if len(addrs) != len(srcs) { |
23 | 30 | panic("internal error") |
24 | 31 | } |
25 | 32 | addrInfos := make([]byRFC6724Info, len(addrs)) |
26 | 33 | for i, v := range addrs { |
27 | 34 | addrAttrIP, _ := netip.AddrFromSlice(v.IP) |
| 35 | + addrAttr := ipAttrOf(addrAttrIP) |
| 36 | + srcAttr := ipAttrOf(srcs[i]) |
28 | 37 | addrInfos[i] = byRFC6724Info{ |
29 | 38 | addr: addrs[i], |
30 | | - addrAttr: ipAttrOf(addrAttrIP), |
| 39 | + addrAttr: addrAttr, |
31 | 40 | src: srcs[i], |
32 | | - srcAttr: ipAttrOf(srcs[i]), |
| 41 | + srcAttr: srcAttr, |
| 42 | + score: scoreByRFC6724(addrs[i].IP, srcs[i], addrAttr, srcAttr, dnsLoadbalance), |
33 | 43 | } |
34 | 44 | } |
| 45 | + |
| 46 | + // shuffle addrInfos before sorting if dnsLoadbalance is true |
| 47 | + // section 6. Rule9 and Rule10 superseded for DNS load-balance indirectly |
| 48 | + if dnsLoadbalance { |
| 49 | + rand.Shuffle(len(addrInfos), func(i, j int) { |
| 50 | + addrInfos[i], addrInfos[j] = addrInfos[j], addrInfos[i] |
| 51 | + }) |
| 52 | + } |
35 | 53 | slices.SortStableFunc(addrInfos, compareByRFC6724) |
| 54 | + |
36 | 55 | for i := range addrInfos { |
37 | 56 | addrs[i] = addrInfos[i].addr |
38 | 57 | } |
@@ -81,130 +100,111 @@ type byRFC6724Info struct { |
81 | 100 | addrAttr ipAttr |
82 | 101 | src netip.Addr |
83 | 102 | srcAttr ipAttr |
| 103 | + // score use to sort to implement RFC 6724 section 6. |
| 104 | + score int |
84 | 105 | } |
85 | 106 |
|
86 | 107 | // compareByRFC6724 compares two byRFC6724Info records and returns an integer |
87 | 108 | // indicating the order. It follows the algorithm and variable names from |
88 | 109 | // RFC 6724 section 6. Returns -1 if a is preferred, 1 if b is preferred, |
89 | 110 | // and 0 if they are equal. |
90 | 111 | func compareByRFC6724(a, b byRFC6724Info) int { |
91 | | - DA := a.addr.IP |
92 | | - DB := b.addr.IP |
93 | | - SourceDA := a.src |
94 | | - SourceDB := b.src |
95 | | - attrDA := &a.addrAttr |
96 | | - attrDB := &b.addrAttr |
97 | | - attrSourceDA := &a.srcAttr |
98 | | - attrSourceDB := &b.srcAttr |
99 | | - |
100 | | - const preferDA = -1 |
101 | | - const preferDB = 1 |
| 112 | + return b.score - a.score |
| 113 | +} |
102 | 114 |
|
103 | | - // Rule 1: Avoid unusable destinations. |
104 | | - // If DB is known to be unreachable or if Source(DB) is undefined, then |
105 | | - // prefer DA. Similarly, if DA is known to be unreachable or if |
106 | | - // Source(DA) is undefined, then prefer DB. |
107 | | - if !SourceDA.IsValid() && !SourceDB.IsValid() { |
108 | | - return 0 // "equal" |
109 | | - } |
110 | | - if !SourceDB.IsValid() { |
111 | | - return preferDA |
112 | | - } |
113 | | - if !SourceDA.IsValid() { |
114 | | - return preferDB |
| 115 | +// scoreByRFC6724 Calculate score with bit in reverse for sort |
| 116 | +// Rules 9 and 10 ignored if ignoreRule9and10 |
| 117 | +func scoreByRFC6724(ip IP, source netip.Addr, attr, attrSource ipAttr, ignoreRule9and10 bool) int { |
| 118 | + var score int |
| 119 | + bitIndex := 1 |
| 120 | + |
| 121 | + // RFC6724 section 6. |
| 122 | + // 6. Destination Address Selection |
| 123 | + // Rule 9: Use longest matching prefix. |
| 124 | + // When DA and DB belong to the same address family (both are IPv6 or |
| 125 | + // both are IPv4): If CommonPrefixLen(Source(DA), DA) > |
| 126 | + // CommonPrefixLen(Source(DB), DB), then prefer DA. Similarly, if |
| 127 | + // CommonPrefixLen(Source(DA), DA) < CommonPrefixLen(Source(DB), DB), |
| 128 | + // then prefer DB. |
| 129 | + // Rule 10: Otherwise, leave the order unchanged. |
| 130 | + // If DA preceded DB in the original list, prefer DA. Otherwise, prefer |
| 131 | + // DB. |
| 132 | + // Rules 9 and 10 MAY be superseded if the implementation has other |
| 133 | + // means of sorting destination addresses. For example, if the |
| 134 | + // implementation somehow knows which destination addresses will result |
| 135 | + // in the "best" communications performance. |
| 136 | + if !ignoreRule9and10 { |
| 137 | + // However, applying this rule to IPv4 addresses causes |
| 138 | + // problems (see issues 13283 and 18518), so limit to IPv6. |
| 139 | + // TODO compare between ipv4 and ipv6 ignoreed insignificant ? |
| 140 | + if ip.To4() == nil { |
| 141 | + score += commonPrefixLen(source, ip) |
| 142 | + } |
115 | 143 | } |
| 144 | + bitIndex += 7 |
116 | 145 |
|
117 | | - // Rule 2: Prefer matching scope. |
118 | | - // If Scope(DA) = Scope(Source(DA)) and Scope(DB) <> Scope(Source(DB)), |
119 | | - // then prefer DA. Similarly, if Scope(DA) <> Scope(Source(DA)) and |
120 | | - // Scope(DB) = Scope(Source(DB)), then prefer DB. |
121 | | - if attrDA.Scope == attrSourceDA.Scope && attrDB.Scope != attrSourceDB.Scope { |
122 | | - return preferDA |
123 | | - } |
124 | | - if attrDA.Scope != attrSourceDA.Scope && attrDB.Scope == attrSourceDB.Scope { |
125 | | - return preferDB |
126 | | - } |
| 146 | + // Rule 8: Prefer smaller scope. |
| 147 | + // If Scope(DA) < Scope(DB), then prefer DA. Similarly, if Scope(DA) > |
| 148 | + // Scope(DB), then prefer DB. |
| 149 | + score += ((1 << 8) - int(attr.Scope)) << bitIndex |
| 150 | + bitIndex += 8 |
127 | 151 |
|
128 | | - // Rule 3: Avoid deprecated addresses. |
129 | | - // If Source(DA) is deprecated and Source(DB) is not, then prefer DB. |
130 | | - // Similarly, if Source(DA) is not deprecated and Source(DB) is |
131 | | - // deprecated, then prefer DA. |
| 152 | + // Rule 7: Prefer native transport. |
| 153 | + // If DA is reached via an encapsulating transition mechanism (e.g., |
| 154 | + // IPv6 in IPv4) and DB is not, then prefer DB. Similarly, if DB is |
| 155 | + // reached via encapsulation and DA is not, then prefer DA. |
132 | 156 |
|
133 | 157 | // TODO(bradfitz): implement? low priority for now. |
134 | 158 |
|
135 | | - // Rule 4: Prefer home addresses. |
136 | | - // If Source(DA) is simultaneously a home address and care-of address |
137 | | - // and Source(DB) is not, then prefer DA. Similarly, if Source(DB) is |
138 | | - // simultaneously a home address and care-of address and Source(DA) is |
139 | | - // not, then prefer DB. |
140 | | - |
141 | | - // TODO(bradfitz): implement? low priority for now. |
| 159 | + // Rule 6: Prefer higher precedence. |
| 160 | + // If Precedence(DA) > Precedence(DB), then prefer DA. Similarly, if |
| 161 | + // Precedence(DA) < Precedence(DB), then prefer DB. |
| 162 | + score += int(attr.Precedence) << bitIndex |
| 163 | + bitIndex += 8 |
142 | 164 |
|
143 | 165 | // Rule 5: Prefer matching label. |
144 | 166 | // If Label(Source(DA)) = Label(DA) and Label(Source(DB)) <> Label(DB), |
145 | 167 | // then prefer DA. Similarly, if Label(Source(DA)) <> Label(DA) and |
146 | 168 | // Label(Source(DB)) = Label(DB), then prefer DB. |
147 | | - if attrSourceDA.Label == attrDA.Label && |
148 | | - attrSourceDB.Label != attrDB.Label { |
149 | | - return preferDA |
150 | | - } |
151 | | - if attrSourceDA.Label != attrDA.Label && |
152 | | - attrSourceDB.Label == attrDB.Label { |
153 | | - return preferDB |
| 169 | + if attrSource.Label == attr.Label { |
| 170 | + score += 1 << bitIndex |
154 | 171 | } |
| 172 | + bitIndex++ |
155 | 173 |
|
156 | | - // Rule 6: Prefer higher precedence. |
157 | | - // If Precedence(DA) > Precedence(DB), then prefer DA. Similarly, if |
158 | | - // Precedence(DA) < Precedence(DB), then prefer DB. |
159 | | - if attrDA.Precedence > attrDB.Precedence { |
160 | | - return preferDA |
161 | | - } |
162 | | - if attrDA.Precedence < attrDB.Precedence { |
163 | | - return preferDB |
164 | | - } |
| 174 | + // Rule 4: Prefer home addresses. |
| 175 | + // If Source(DA) is simultaneously a home address and care-of address |
| 176 | + // and Source(DB) is not, then prefer DA. Similarly, if Source(DB) is |
| 177 | + // simultaneously a home address and care-of address and Source(DA) is |
| 178 | + // not, then prefer DB. |
165 | 179 |
|
166 | | - // Rule 7: Prefer native transport. |
167 | | - // If DA is reached via an encapsulating transition mechanism (e.g., |
168 | | - // IPv6 in IPv4) and DB is not, then prefer DB. Similarly, if DB is |
169 | | - // reached via encapsulation and DA is not, then prefer DA. |
| 180 | + // TODO(bradfitz): implement? low priority for now. |
| 181 | + |
| 182 | + // Rule 3: Avoid deprecated addresses. |
| 183 | + // If Source(DA) is deprecated and Source(DB) is not, then prefer DB. |
| 184 | + // Similarly, if Source(DA) is not deprecated and Source(DB) is |
| 185 | + // deprecated, then prefer DA. |
170 | 186 |
|
171 | 187 | // TODO(bradfitz): implement? low priority for now. |
172 | 188 |
|
173 | | - // Rule 8: Prefer smaller scope. |
174 | | - // If Scope(DA) < Scope(DB), then prefer DA. Similarly, if Scope(DA) > |
175 | | - // Scope(DB), then prefer DB. |
176 | | - if attrDA.Scope < attrDB.Scope { |
177 | | - return preferDA |
178 | | - } |
179 | | - if attrDA.Scope > attrDB.Scope { |
180 | | - return preferDB |
| 189 | + // Rule 2: Prefer matching scope. |
| 190 | + // If Scope(DA) = Scope(Source(DA)) and Scope(DB) <> Scope(Source(DB)), |
| 191 | + // then prefer DA. Similarly, if Scope(DA) <> Scope(Source(DA)) and |
| 192 | + // Scope(DB) = Scope(Source(DB)), then prefer DB. |
| 193 | + if attr.Scope == attrSource.Scope { |
| 194 | + score += 1 << bitIndex |
181 | 195 | } |
| 196 | + bitIndex++ |
182 | 197 |
|
183 | | - // Rule 9: Use the longest matching prefix. |
184 | | - // When DA and DB belong to the same address family (both are IPv6 or |
185 | | - // both are IPv4 [but see below]): If CommonPrefixLen(Source(DA), DA) > |
186 | | - // CommonPrefixLen(Source(DB), DB), then prefer DA. Similarly, if |
187 | | - // CommonPrefixLen(Source(DA), DA) < CommonPrefixLen(Source(DB), DB), |
188 | | - // then prefer DB. |
189 | | - // |
190 | | - // However, applying this rule to IPv4 addresses causes |
191 | | - // problems (see issues 13283 and 18518), so limit to IPv6. |
192 | | - if DA.To4() == nil && DB.To4() == nil { |
193 | | - commonA := commonPrefixLen(SourceDA, DA) |
194 | | - commonB := commonPrefixLen(SourceDB, DB) |
195 | | - |
196 | | - if commonA > commonB { |
197 | | - return preferDA |
198 | | - } |
199 | | - if commonA < commonB { |
200 | | - return preferDB |
201 | | - } |
| 198 | + // Rule 1: Avoid unusable destinations. |
| 199 | + // If DB is known to be unreachable or if Source(DB) is undefined, then |
| 200 | + // prefer DA. Similarly, if DA is known to be unreachable or if |
| 201 | + // Source(DA) is undefined, then prefer DB. |
| 202 | + if source.IsValid() { |
| 203 | + score += 1 << bitIndex |
202 | 204 | } |
| 205 | + bitIndex++ |
203 | 206 |
|
204 | | - // Rule 10: Otherwise, leave the order unchanged. |
205 | | - // If DA preceded DB in the original list, prefer DA. |
206 | | - // Otherwise, prefer DB. |
207 | | - return 0 // "equal" |
| 207 | + return score |
208 | 208 | } |
209 | 209 |
|
210 | 210 | type policyTableEntry struct { |
|
0 commit comments