github.com/slackhq/nebula@v1.9.0/remote_list.go (about) 1 package nebula 2 3 import ( 4 "bytes" 5 "context" 6 "net" 7 "net/netip" 8 "sort" 9 "strconv" 10 "sync" 11 "sync/atomic" 12 "time" 13 14 "github.com/sirupsen/logrus" 15 "github.com/slackhq/nebula/iputil" 16 "github.com/slackhq/nebula/udp" 17 ) 18 19 // forEachFunc is used to benefit folks that want to do work inside the lock 20 type forEachFunc func(addr *udp.Addr, preferred bool) 21 22 // The checkFuncs here are to simplify bulk importing LH query response logic into a single function (reset slice and iterate) 23 type checkFuncV4 func(vpnIp iputil.VpnIp, to *Ip4AndPort) bool 24 type checkFuncV6 func(vpnIp iputil.VpnIp, to *Ip6AndPort) bool 25 26 // CacheMap is a struct that better represents the lighthouse cache for humans 27 // The string key is the owners vpnIp 28 type CacheMap map[string]*Cache 29 30 // Cache is the other part of CacheMap to better represent the lighthouse cache for humans 31 // We don't reason about ipv4 vs ipv6 here 32 type Cache struct { 33 Learned []*udp.Addr `json:"learned,omitempty"` 34 Reported []*udp.Addr `json:"reported,omitempty"` 35 Relay []*net.IP `json:"relay"` 36 } 37 38 //TODO: Seems like we should plop static host entries in here too since the are protected by the lighthouse from deletion 39 // We will never clean learned/reported information for them as it stands today 40 41 // cache is an internal struct that splits v4 and v6 addresses inside the cache map 42 type cache struct { 43 v4 *cacheV4 44 v6 *cacheV6 45 relay *cacheRelay 46 } 47 48 type cacheRelay struct { 49 relay []uint32 50 } 51 52 // cacheV4 stores learned and reported ipv4 records under cache 53 type cacheV4 struct { 54 learned *Ip4AndPort 55 reported []*Ip4AndPort 56 } 57 58 // cacheV4 stores learned and reported ipv6 records under cache 59 type cacheV6 struct { 60 learned *Ip6AndPort 61 reported []*Ip6AndPort 62 } 63 64 type hostnamePort struct { 65 name string 66 port uint16 67 } 68 69 type hostnamesResults struct { 70 hostnames []hostnamePort 71 network string 72 lookupTimeout time.Duration 73 cancelFn func() 74 l *logrus.Logger 75 ips atomic.Pointer[map[netip.AddrPort]struct{}] 76 } 77 78 func NewHostnameResults(ctx context.Context, l *logrus.Logger, d time.Duration, network string, timeout time.Duration, hostPorts []string, onUpdate func()) (*hostnamesResults, error) { 79 r := &hostnamesResults{ 80 hostnames: make([]hostnamePort, len(hostPorts)), 81 network: network, 82 lookupTimeout: timeout, 83 l: l, 84 } 85 86 // Fastrack IP addresses to ensure they're immediately available for use. 87 // DNS lookups for hostnames that aren't hardcoded IP's will happen in a background goroutine. 88 performBackgroundLookup := false 89 ips := map[netip.AddrPort]struct{}{} 90 for idx, hostPort := range hostPorts { 91 92 rIp, sPort, err := net.SplitHostPort(hostPort) 93 if err != nil { 94 return nil, err 95 } 96 97 iPort, err := strconv.Atoi(sPort) 98 if err != nil { 99 return nil, err 100 } 101 102 r.hostnames[idx] = hostnamePort{name: rIp, port: uint16(iPort)} 103 addr, err := netip.ParseAddr(rIp) 104 if err != nil { 105 // This address is a hostname, not an IP address 106 performBackgroundLookup = true 107 continue 108 } 109 110 // Save the IP address immediately 111 ips[netip.AddrPortFrom(addr, uint16(iPort))] = struct{}{} 112 } 113 r.ips.Store(&ips) 114 115 // Time for the DNS lookup goroutine 116 if performBackgroundLookup { 117 newCtx, cancel := context.WithCancel(ctx) 118 r.cancelFn = cancel 119 ticker := time.NewTicker(d) 120 go func() { 121 defer ticker.Stop() 122 for { 123 netipAddrs := map[netip.AddrPort]struct{}{} 124 for _, hostPort := range r.hostnames { 125 timeoutCtx, timeoutCancel := context.WithTimeout(ctx, r.lookupTimeout) 126 addrs, err := net.DefaultResolver.LookupNetIP(timeoutCtx, r.network, hostPort.name) 127 timeoutCancel() 128 if err != nil { 129 l.WithFields(logrus.Fields{"hostname": hostPort.name, "network": r.network}).WithError(err).Error("DNS resolution failed for static_map host") 130 continue 131 } 132 for _, a := range addrs { 133 netipAddrs[netip.AddrPortFrom(a, hostPort.port)] = struct{}{} 134 } 135 } 136 origSet := r.ips.Load() 137 different := false 138 for a := range *origSet { 139 if _, ok := netipAddrs[a]; !ok { 140 different = true 141 break 142 } 143 } 144 if !different { 145 for a := range netipAddrs { 146 if _, ok := (*origSet)[a]; !ok { 147 different = true 148 break 149 } 150 } 151 } 152 if different { 153 l.WithFields(logrus.Fields{"origSet": origSet, "newSet": netipAddrs}).Info("DNS results changed for host list") 154 r.ips.Store(&netipAddrs) 155 onUpdate() 156 } 157 select { 158 case <-newCtx.Done(): 159 return 160 case <-ticker.C: 161 continue 162 } 163 } 164 }() 165 } 166 167 return r, nil 168 } 169 170 func (hr *hostnamesResults) Cancel() { 171 if hr != nil && hr.cancelFn != nil { 172 hr.cancelFn() 173 } 174 } 175 176 func (hr *hostnamesResults) GetIPs() []netip.AddrPort { 177 var retSlice []netip.AddrPort 178 if hr != nil { 179 p := hr.ips.Load() 180 if p != nil { 181 for k := range *p { 182 retSlice = append(retSlice, k) 183 } 184 } 185 } 186 return retSlice 187 } 188 189 // RemoteList is a unifying concept for lighthouse servers and clients as well as hostinfos. 190 // It serves as a local cache of query replies, host update notifications, and locally learned addresses 191 type RemoteList struct { 192 // Every interaction with internals requires a lock! 193 sync.RWMutex 194 195 // A deduplicated set of addresses. Any accessor should lock beforehand. 196 addrs []*udp.Addr 197 198 // A set of relay addresses. VpnIp addresses that the remote identified as relays. 199 relays []*iputil.VpnIp 200 201 // These are maps to store v4 and v6 addresses per lighthouse 202 // Map key is the vpnIp of the person that told us about this the cached entries underneath. 203 // For learned addresses, this is the vpnIp that sent the packet 204 cache map[iputil.VpnIp]*cache 205 206 hr *hostnamesResults 207 shouldAdd func(netip.Addr) bool 208 209 // This is a list of remotes that we have tried to handshake with and have returned from the wrong vpn ip. 210 // They should not be tried again during a handshake 211 badRemotes []*udp.Addr 212 213 // A flag that the cache may have changed and addrs needs to be rebuilt 214 shouldRebuild bool 215 } 216 217 // NewRemoteList creates a new empty RemoteList 218 func NewRemoteList(shouldAdd func(netip.Addr) bool) *RemoteList { 219 return &RemoteList{ 220 addrs: make([]*udp.Addr, 0), 221 relays: make([]*iputil.VpnIp, 0), 222 cache: make(map[iputil.VpnIp]*cache), 223 shouldAdd: shouldAdd, 224 } 225 } 226 227 func (r *RemoteList) unlockedSetHostnamesResults(hr *hostnamesResults) { 228 // Cancel any existing hostnamesResults DNS goroutine to release resources 229 r.hr.Cancel() 230 r.hr = hr 231 } 232 233 // Len locks and reports the size of the deduplicated address list 234 // The deduplication work may need to occur here, so you must pass preferredRanges 235 func (r *RemoteList) Len(preferredRanges []*net.IPNet) int { 236 r.Rebuild(preferredRanges) 237 r.RLock() 238 defer r.RUnlock() 239 return len(r.addrs) 240 } 241 242 // ForEach locks and will call the forEachFunc for every deduplicated address in the list 243 // The deduplication work may need to occur here, so you must pass preferredRanges 244 func (r *RemoteList) ForEach(preferredRanges []*net.IPNet, forEach forEachFunc) { 245 r.Rebuild(preferredRanges) 246 r.RLock() 247 for _, v := range r.addrs { 248 forEach(v, isPreferred(v.IP, preferredRanges)) 249 } 250 r.RUnlock() 251 } 252 253 // CopyAddrs locks and makes a deep copy of the deduplicated address list 254 // The deduplication work may need to occur here, so you must pass preferredRanges 255 func (r *RemoteList) CopyAddrs(preferredRanges []*net.IPNet) []*udp.Addr { 256 if r == nil { 257 return nil 258 } 259 260 r.Rebuild(preferredRanges) 261 262 r.RLock() 263 defer r.RUnlock() 264 c := make([]*udp.Addr, len(r.addrs)) 265 for i, v := range r.addrs { 266 c[i] = v.Copy() 267 } 268 return c 269 } 270 271 // LearnRemote locks and sets the learned slot for the owner vpn ip to the provided addr 272 // Currently this is only needed when HostInfo.SetRemote is called as that should cover both handshaking and roaming. 273 // It will mark the deduplicated address list as dirty, so do not call it unless new information is available 274 // TODO: this needs to support the allow list list 275 func (r *RemoteList) LearnRemote(ownerVpnIp iputil.VpnIp, addr *udp.Addr) { 276 r.Lock() 277 defer r.Unlock() 278 if v4 := addr.IP.To4(); v4 != nil { 279 r.unlockedSetLearnedV4(ownerVpnIp, NewIp4AndPort(v4, uint32(addr.Port))) 280 } else { 281 r.unlockedSetLearnedV6(ownerVpnIp, NewIp6AndPort(addr.IP, uint32(addr.Port))) 282 } 283 } 284 285 // CopyCache locks and creates a more human friendly form of the internal address cache. 286 // This may contain duplicates and blocked addresses 287 func (r *RemoteList) CopyCache() *CacheMap { 288 r.RLock() 289 defer r.RUnlock() 290 291 cm := make(CacheMap) 292 getOrMake := func(vpnIp string) *Cache { 293 c := cm[vpnIp] 294 if c == nil { 295 c = &Cache{ 296 Learned: make([]*udp.Addr, 0), 297 Reported: make([]*udp.Addr, 0), 298 Relay: make([]*net.IP, 0), 299 } 300 cm[vpnIp] = c 301 } 302 return c 303 } 304 305 for owner, mc := range r.cache { 306 c := getOrMake(owner.String()) 307 308 if mc.v4 != nil { 309 if mc.v4.learned != nil { 310 c.Learned = append(c.Learned, NewUDPAddrFromLH4(mc.v4.learned)) 311 } 312 313 for _, a := range mc.v4.reported { 314 c.Reported = append(c.Reported, NewUDPAddrFromLH4(a)) 315 } 316 } 317 318 if mc.v6 != nil { 319 if mc.v6.learned != nil { 320 c.Learned = append(c.Learned, NewUDPAddrFromLH6(mc.v6.learned)) 321 } 322 323 for _, a := range mc.v6.reported { 324 c.Reported = append(c.Reported, NewUDPAddrFromLH6(a)) 325 } 326 } 327 328 if mc.relay != nil { 329 for _, a := range mc.relay.relay { 330 nip := iputil.VpnIp(a).ToIP() 331 c.Relay = append(c.Relay, &nip) 332 } 333 } 334 } 335 336 return &cm 337 } 338 339 // BlockRemote locks and records the address as bad, it will be excluded from the deduplicated address list 340 func (r *RemoteList) BlockRemote(bad *udp.Addr) { 341 if bad == nil { 342 // relays can have nil udp Addrs 343 return 344 } 345 r.Lock() 346 defer r.Unlock() 347 348 // Check if we already blocked this addr 349 if r.unlockedIsBad(bad) { 350 return 351 } 352 353 // We copy here because we are taking something else's memory and we can't trust everything 354 r.badRemotes = append(r.badRemotes, bad.Copy()) 355 356 // Mark the next interaction must recollect/dedupe 357 r.shouldRebuild = true 358 } 359 360 // CopyBlockedRemotes locks and makes a deep copy of the blocked remotes list 361 func (r *RemoteList) CopyBlockedRemotes() []*udp.Addr { 362 r.RLock() 363 defer r.RUnlock() 364 365 c := make([]*udp.Addr, len(r.badRemotes)) 366 for i, v := range r.badRemotes { 367 c[i] = v.Copy() 368 } 369 return c 370 } 371 372 // ResetBlockedRemotes locks and clears the blocked remotes list 373 func (r *RemoteList) ResetBlockedRemotes() { 374 r.Lock() 375 r.badRemotes = nil 376 r.Unlock() 377 } 378 379 // Rebuild locks and generates the deduplicated address list only if there is work to be done 380 // There is generally no reason to call this directly but it is safe to do so 381 func (r *RemoteList) Rebuild(preferredRanges []*net.IPNet) { 382 r.Lock() 383 defer r.Unlock() 384 385 // Only rebuild if the cache changed 386 //TODO: shouldRebuild is probably pointless as we don't check for actual change when lighthouse updates come in 387 if r.shouldRebuild { 388 r.unlockedCollect() 389 r.shouldRebuild = false 390 } 391 392 // Always re-sort, preferredRanges can change via HUP 393 r.unlockedSort(preferredRanges) 394 } 395 396 // unlockedIsBad assumes you have the write lock and checks if the remote matches any entry in the blocked address list 397 func (r *RemoteList) unlockedIsBad(remote *udp.Addr) bool { 398 for _, v := range r.badRemotes { 399 if v.Equals(remote) { 400 return true 401 } 402 } 403 return false 404 } 405 406 // unlockedSetLearnedV4 assumes you have the write lock and sets the current learned address for this owner and marks the 407 // deduplicated address list as dirty 408 func (r *RemoteList) unlockedSetLearnedV4(ownerVpnIp iputil.VpnIp, to *Ip4AndPort) { 409 r.shouldRebuild = true 410 r.unlockedGetOrMakeV4(ownerVpnIp).learned = to 411 } 412 413 // unlockedSetV4 assumes you have the write lock and resets the reported list of ips for this owner to the list provided 414 // and marks the deduplicated address list as dirty 415 func (r *RemoteList) unlockedSetV4(ownerVpnIp iputil.VpnIp, vpnIp iputil.VpnIp, to []*Ip4AndPort, check checkFuncV4) { 416 r.shouldRebuild = true 417 c := r.unlockedGetOrMakeV4(ownerVpnIp) 418 419 // Reset the slice 420 c.reported = c.reported[:0] 421 422 // We can't take their array but we can take their pointers 423 for _, v := range to[:minInt(len(to), MaxRemotes)] { 424 if check(vpnIp, v) { 425 c.reported = append(c.reported, v) 426 } 427 } 428 } 429 430 func (r *RemoteList) unlockedSetRelay(ownerVpnIp iputil.VpnIp, vpnIp iputil.VpnIp, to []uint32) { 431 r.shouldRebuild = true 432 c := r.unlockedGetOrMakeRelay(ownerVpnIp) 433 434 // Reset the slice 435 c.relay = c.relay[:0] 436 437 // We can't take their array but we can take their pointers 438 c.relay = append(c.relay, to[:minInt(len(to), MaxRemotes)]...) 439 } 440 441 // unlockedPrependV4 assumes you have the write lock and prepends the address in the reported list for this owner 442 // This is only useful for establishing static hosts 443 func (r *RemoteList) unlockedPrependV4(ownerVpnIp iputil.VpnIp, to *Ip4AndPort) { 444 r.shouldRebuild = true 445 c := r.unlockedGetOrMakeV4(ownerVpnIp) 446 447 // We are doing the easy append because this is rarely called 448 c.reported = append([]*Ip4AndPort{to}, c.reported...) 449 if len(c.reported) > MaxRemotes { 450 c.reported = c.reported[:MaxRemotes] 451 } 452 } 453 454 // unlockedSetLearnedV6 assumes you have the write lock and sets the current learned address for this owner and marks the 455 // deduplicated address list as dirty 456 func (r *RemoteList) unlockedSetLearnedV6(ownerVpnIp iputil.VpnIp, to *Ip6AndPort) { 457 r.shouldRebuild = true 458 r.unlockedGetOrMakeV6(ownerVpnIp).learned = to 459 } 460 461 // unlockedSetV6 assumes you have the write lock and resets the reported list of ips for this owner to the list provided 462 // and marks the deduplicated address list as dirty 463 func (r *RemoteList) unlockedSetV6(ownerVpnIp iputil.VpnIp, vpnIp iputil.VpnIp, to []*Ip6AndPort, check checkFuncV6) { 464 r.shouldRebuild = true 465 c := r.unlockedGetOrMakeV6(ownerVpnIp) 466 467 // Reset the slice 468 c.reported = c.reported[:0] 469 470 // We can't take their array but we can take their pointers 471 for _, v := range to[:minInt(len(to), MaxRemotes)] { 472 if check(vpnIp, v) { 473 c.reported = append(c.reported, v) 474 } 475 } 476 } 477 478 // unlockedPrependV6 assumes you have the write lock and prepends the address in the reported list for this owner 479 // This is only useful for establishing static hosts 480 func (r *RemoteList) unlockedPrependV6(ownerVpnIp iputil.VpnIp, to *Ip6AndPort) { 481 r.shouldRebuild = true 482 c := r.unlockedGetOrMakeV6(ownerVpnIp) 483 484 // We are doing the easy append because this is rarely called 485 c.reported = append([]*Ip6AndPort{to}, c.reported...) 486 if len(c.reported) > MaxRemotes { 487 c.reported = c.reported[:MaxRemotes] 488 } 489 } 490 491 func (r *RemoteList) unlockedGetOrMakeRelay(ownerVpnIp iputil.VpnIp) *cacheRelay { 492 am := r.cache[ownerVpnIp] 493 if am == nil { 494 am = &cache{} 495 r.cache[ownerVpnIp] = am 496 } 497 // Avoid occupying memory for relay if we never have any 498 if am.relay == nil { 499 am.relay = &cacheRelay{} 500 } 501 return am.relay 502 } 503 504 // unlockedGetOrMakeV4 assumes you have the write lock and builds the cache and owner entry. Only the v4 pointer is established. 505 // The caller must dirty the learned address cache if required 506 func (r *RemoteList) unlockedGetOrMakeV4(ownerVpnIp iputil.VpnIp) *cacheV4 { 507 am := r.cache[ownerVpnIp] 508 if am == nil { 509 am = &cache{} 510 r.cache[ownerVpnIp] = am 511 } 512 // Avoid occupying memory for v6 addresses if we never have any 513 if am.v4 == nil { 514 am.v4 = &cacheV4{} 515 } 516 return am.v4 517 } 518 519 // unlockedGetOrMakeV6 assumes you have the write lock and builds the cache and owner entry. Only the v6 pointer is established. 520 // The caller must dirty the learned address cache if required 521 func (r *RemoteList) unlockedGetOrMakeV6(ownerVpnIp iputil.VpnIp) *cacheV6 { 522 am := r.cache[ownerVpnIp] 523 if am == nil { 524 am = &cache{} 525 r.cache[ownerVpnIp] = am 526 } 527 // Avoid occupying memory for v4 addresses if we never have any 528 if am.v6 == nil { 529 am.v6 = &cacheV6{} 530 } 531 return am.v6 532 } 533 534 // unlockedCollect assumes you have the write lock and collects/transforms the cache into the deduped address list. 535 // The result of this function can contain duplicates. unlockedSort handles cleaning it. 536 func (r *RemoteList) unlockedCollect() { 537 addrs := r.addrs[:0] 538 relays := r.relays[:0] 539 540 for _, c := range r.cache { 541 if c.v4 != nil { 542 if c.v4.learned != nil { 543 u := NewUDPAddrFromLH4(c.v4.learned) 544 if !r.unlockedIsBad(u) { 545 addrs = append(addrs, u) 546 } 547 } 548 549 for _, v := range c.v4.reported { 550 u := NewUDPAddrFromLH4(v) 551 if !r.unlockedIsBad(u) { 552 addrs = append(addrs, u) 553 } 554 } 555 } 556 557 if c.v6 != nil { 558 if c.v6.learned != nil { 559 u := NewUDPAddrFromLH6(c.v6.learned) 560 if !r.unlockedIsBad(u) { 561 addrs = append(addrs, u) 562 } 563 } 564 565 for _, v := range c.v6.reported { 566 u := NewUDPAddrFromLH6(v) 567 if !r.unlockedIsBad(u) { 568 addrs = append(addrs, u) 569 } 570 } 571 } 572 573 if c.relay != nil { 574 for _, v := range c.relay.relay { 575 ip := iputil.VpnIp(v) 576 relays = append(relays, &ip) 577 } 578 } 579 } 580 581 dnsAddrs := r.hr.GetIPs() 582 for _, addr := range dnsAddrs { 583 if r.shouldAdd == nil || r.shouldAdd(addr.Addr()) { 584 v6 := addr.Addr().As16() 585 addrs = append(addrs, &udp.Addr{ 586 IP: v6[:], 587 Port: addr.Port(), 588 }) 589 } 590 } 591 592 r.addrs = addrs 593 r.relays = relays 594 595 } 596 597 // unlockedSort assumes you have the write lock and performs the deduping and sorting of the address list 598 func (r *RemoteList) unlockedSort(preferredRanges []*net.IPNet) { 599 n := len(r.addrs) 600 if n < 2 { 601 return 602 } 603 604 lessFunc := func(i, j int) bool { 605 a := r.addrs[i] 606 b := r.addrs[j] 607 // Preferred addresses first 608 609 aPref := isPreferred(a.IP, preferredRanges) 610 bPref := isPreferred(b.IP, preferredRanges) 611 switch { 612 case aPref && !bPref: 613 // If i is preferred and j is not, i is less than j 614 return true 615 616 case !aPref && bPref: 617 // If j is preferred then i is not due to the else, i is not less than j 618 return false 619 620 default: 621 // Both i an j are either preferred or not, sort within that 622 } 623 624 // ipv6 addresses 2nd 625 a4 := a.IP.To4() 626 b4 := b.IP.To4() 627 switch { 628 case a4 == nil && b4 != nil: 629 // If i is v6 and j is v4, i is less than j 630 return true 631 632 case a4 != nil && b4 == nil: 633 // If j is v6 and i is v4, i is not less than j 634 return false 635 636 case a4 != nil && b4 != nil: 637 // Special case for ipv4, a4 and b4 are not nil 638 aPrivate := isPrivateIP(a4) 639 bPrivate := isPrivateIP(b4) 640 switch { 641 case !aPrivate && bPrivate: 642 // If i is a public ip (not private) and j is a private ip, i is less then j 643 return true 644 645 case aPrivate && !bPrivate: 646 // If j is public (not private) then i is private due to the else, i is not less than j 647 return false 648 649 default: 650 // Both i an j are either public or private, sort within that 651 } 652 653 default: 654 // Both i an j are either ipv4 or ipv6, sort within that 655 } 656 657 // lexical order of ips 3rd 658 c := bytes.Compare(a.IP, b.IP) 659 if c == 0 { 660 // Ips are the same, Lexical order of ports 4th 661 return a.Port < b.Port 662 } 663 664 // Ip wasn't the same 665 return c < 0 666 } 667 668 // Sort it 669 sort.Slice(r.addrs, lessFunc) 670 671 // Deduplicate 672 a, b := 0, 1 673 for b < n { 674 if !r.addrs[a].Equals(r.addrs[b]) { 675 a++ 676 if a != b { 677 r.addrs[a], r.addrs[b] = r.addrs[b], r.addrs[a] 678 } 679 } 680 b++ 681 } 682 683 r.addrs = r.addrs[:a+1] 684 return 685 } 686 687 // minInt returns the minimum integer of a or b 688 func minInt(a, b int) int { 689 if a < b { 690 return a 691 } 692 return b 693 } 694 695 // isPreferred returns true of the ip is contained in the preferredRanges list 696 func isPreferred(ip net.IP, preferredRanges []*net.IPNet) bool { 697 //TODO: this would be better in a CIDR6Tree 698 for _, p := range preferredRanges { 699 if p.Contains(ip) { 700 return true 701 } 702 } 703 return false 704 } 705 706 var _, private24BitBlock, _ = net.ParseCIDR("10.0.0.0/8") 707 var _, private20BitBlock, _ = net.ParseCIDR("172.16.0.0/12") 708 var _, private16BitBlock, _ = net.ParseCIDR("192.168.0.0/16") 709 710 // isPrivateIP returns true if the ip is contained by a rfc 1918 private range 711 func isPrivateIP(ip net.IP) bool { 712 //TODO: another great cidrtree option 713 //TODO: Private for ipv6 or just let it ride? 714 return private24BitBlock.Contains(ip) || private20BitBlock.Contains(ip) || private16BitBlock.Contains(ip) 715 }