github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/network/address.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package network 5 6 import ( 7 "bytes" 8 "encoding/binary" 9 "fmt" 10 "net" 11 "sort" 12 "strings" 13 14 "github.com/juju/errors" 15 "github.com/juju/utils/set" 16 ) 17 18 // Private network ranges for IPv4 and IPv6. 19 // See: http://tools.ietf.org/html/rfc1918 20 // Also: http://tools.ietf.org/html/rfc4193 21 var ( 22 classAPrivate = mustParseCIDR("10.0.0.0/8") 23 classBPrivate = mustParseCIDR("172.16.0.0/12") 24 classCPrivate = mustParseCIDR("192.168.0.0/16") 25 ipv6UniqueLocal = mustParseCIDR("fc00::/7") 26 ) 27 28 const ( 29 // LoopbackIPv4CIDR is the loopback CIDR range for IPv4. 30 LoopbackIPv4CIDR = "127.0.0.0/8" 31 32 // LoopbackIPv6CIDR is the loopback CIDR range for IPv6. 33 LoopbackIPv6CIDR = "::1/128" 34 ) 35 36 func mustParseCIDR(s string) *net.IPNet { 37 _, net, err := net.ParseCIDR(s) 38 if err != nil { 39 panic(err) 40 } 41 return net 42 } 43 44 // AddressType represents the possible ways of specifying a machine location by 45 // either a hostname resolvable by dns lookup, or IPv4 or IPv6 address. 46 type AddressType string 47 48 const ( 49 HostName AddressType = "hostname" 50 IPv4Address AddressType = "ipv4" 51 IPv6Address AddressType = "ipv6" 52 ) 53 54 // Scope denotes the context a location may apply to. If a name or 55 // address can be reached from the wider internet, it is considered 56 // public. A private network address is either specific to the cloud 57 // or cloud subnet a machine belongs to, or to the machine itself for 58 // containers. 59 type Scope string 60 61 // SpaceName holds the Juju space name of an address. 62 type SpaceName string 63 type spaceNameList []SpaceName 64 65 func (s spaceNameList) String() string { 66 namesString := make([]string, len(s)) 67 for i, v := range s { 68 namesString[i] = string(v) 69 } 70 71 return strings.Join(namesString, ", ") 72 } 73 74 func (s spaceNameList) IndexOf(name SpaceName) int { 75 for i := range s { 76 if s[i] == name { 77 return i 78 } 79 } 80 return -1 81 } 82 83 const ( 84 ScopeUnknown Scope = "" 85 ScopePublic Scope = "public" 86 ScopeCloudLocal Scope = "local-cloud" 87 ScopeMachineLocal Scope = "local-machine" 88 ScopeLinkLocal Scope = "link-local" 89 ) 90 91 // Address represents the location of a machine, including metadata 92 // about what kind of location the address describes. 93 type Address struct { 94 Value string 95 Type AddressType 96 Scope 97 SpaceName 98 SpaceProviderId Id 99 } 100 101 // String returns a string representation of the address, in the form: 102 // `<scope>:<address-value>@<space-name>(id:<space-provider-id)`; for example: 103 // 104 // public:c2-54-226-162-124.compute-1.amazonaws.com@public-api(id:42) 105 // 106 // If the scope is ScopeUnknown, the initial "<scope>:" prefix will be omitted. 107 // If the SpaceName is blank, the "@<space-name>" suffix will be omitted. 108 // Finally, if the SpaceProviderId is empty the suffix 109 // "(id:<space-provider-id>)" part will be omitted as well. 110 func (a Address) String() string { 111 var buf bytes.Buffer 112 if a.Scope != ScopeUnknown { 113 buf.WriteString(string(a.Scope)) 114 buf.WriteByte(':') 115 } 116 buf.WriteString(a.Value) 117 118 var spaceFound bool 119 if a.SpaceName != "" { 120 spaceFound = true 121 buf.WriteByte('@') 122 buf.WriteString(string(a.SpaceName)) 123 } 124 if a.SpaceProviderId != Id("") { 125 if !spaceFound { 126 buf.WriteByte('@') 127 } 128 buf.WriteString(fmt.Sprintf("(id:%v)", string(a.SpaceProviderId))) 129 } 130 return buf.String() 131 } 132 133 // GoString implements fmt.GoStringer. 134 func (a Address) GoString() string { 135 return a.String() 136 } 137 138 // NewAddress creates a new Address, deriving its type from the value 139 // and using ScopeUnknown as scope. It's a shortcut to calling 140 // NewScopedAddress(value, ScopeUnknown). 141 func NewAddress(value string) Address { 142 return NewScopedAddress(value, ScopeUnknown) 143 } 144 145 // NewScopedAddress creates a new Address, deriving its type from the 146 // value. 147 // 148 // If the specified scope is ScopeUnknown, then NewScopedAddress will 149 // attempt derive the scope based on reserved IP address ranges. 150 // Because passing ScopeUnknown is fairly common, NewAddress() above 151 // does exactly that. 152 func NewScopedAddress(value string, scope Scope) Address { 153 addr := Address{ 154 Value: value, 155 Type: DeriveAddressType(value), 156 Scope: scope, 157 } 158 if scope == ScopeUnknown { 159 addr.Scope = deriveScope(addr) 160 } 161 return addr 162 } 163 164 // NewAddressOnSpace creates a new Address, deriving its type and scope from the 165 // value and associating it with the given spaceName. 166 func NewAddressOnSpace(spaceName string, value string) Address { 167 addr := NewAddress(value) 168 addr.SpaceName = SpaceName(spaceName) 169 return addr 170 } 171 172 // NewAddresses is a convenience function to create addresses from a a variable 173 // number of string arguments. 174 func NewAddresses(inAddresses ...string) (outAddresses []Address) { 175 outAddresses = make([]Address, len(inAddresses)) 176 for i, address := range inAddresses { 177 outAddresses[i] = NewAddress(address) 178 } 179 return outAddresses 180 } 181 182 // NewAddressesOnSpace is a convenience function to create addresses on the same 183 // space, from a a variable number of string arguments. 184 func NewAddressesOnSpace(spaceName string, inAddresses ...string) (outAddresses []Address) { 185 outAddresses = make([]Address, len(inAddresses)) 186 for i, address := range inAddresses { 187 outAddresses[i] = NewAddressOnSpace(spaceName, address) 188 } 189 return outAddresses 190 } 191 192 // DeriveAddressType attempts to detect the type of address given. 193 func DeriveAddressType(value string) AddressType { 194 ip := net.ParseIP(value) 195 switch { 196 case ip == nil: 197 // TODO(gz): Check value is a valid hostname 198 return HostName 199 case ip.To4() != nil: 200 return IPv4Address 201 case ip.To16() != nil: 202 return IPv6Address 203 default: 204 panic("Unknown form of IP address") 205 } 206 } 207 208 func isIPv4PrivateNetworkAddress(addrType AddressType, ip net.IP) bool { 209 if addrType != IPv4Address { 210 return false 211 } 212 return classAPrivate.Contains(ip) || 213 classBPrivate.Contains(ip) || 214 classCPrivate.Contains(ip) 215 } 216 217 func isIPv6UniqueLocalAddress(addrType AddressType, ip net.IP) bool { 218 if addrType != IPv6Address { 219 return false 220 } 221 return ipv6UniqueLocal.Contains(ip) 222 } 223 224 // deriveScope attempts to derive the network scope from an address's 225 // type and value, returning the original network scope if no 226 // deduction can be made. 227 func deriveScope(addr Address) Scope { 228 if addr.Type == HostName { 229 return addr.Scope 230 } 231 ip := net.ParseIP(addr.Value) 232 if ip == nil { 233 return addr.Scope 234 } 235 if ip.IsLoopback() { 236 return ScopeMachineLocal 237 } 238 if isIPv4PrivateNetworkAddress(addr.Type, ip) || 239 isIPv6UniqueLocalAddress(addr.Type, ip) { 240 return ScopeCloudLocal 241 } 242 if ip.IsLinkLocalMulticast() || 243 ip.IsLinkLocalUnicast() || 244 ip.IsInterfaceLocalMulticast() { 245 return ScopeLinkLocal 246 } 247 if ip.IsGlobalUnicast() { 248 return ScopePublic 249 } 250 return addr.Scope 251 } 252 253 // ExactScopeMatch checks if an address exactly matches any of the specified 254 // scopes. 255 func ExactScopeMatch(addr Address, addrScopes ...Scope) bool { 256 for _, scope := range addrScopes { 257 if addr.Scope == scope { 258 return true 259 } 260 } 261 return false 262 } 263 264 // SelectAddressBySpaces picks the first address from the given slice that has 265 // the given space name associated. 266 func SelectAddressBySpaces(addresses []Address, spaceNames ...SpaceName) (Address, bool) { 267 for _, addr := range addresses { 268 if spaceNameList(spaceNames).IndexOf(addr.SpaceName) >= 0 { 269 logger.Debugf("selected %q as first address in space %q", addr.Value, addr.SpaceName) 270 return addr, true 271 } 272 } 273 274 if len(spaceNames) == 0 { 275 logger.Errorf("no spaces to select addresses from") 276 } else { 277 logger.Errorf("no addresses found in spaces %s", spaceNames) 278 } 279 return Address{}, false 280 } 281 282 // SelectHostsPortBySpaces picks the first HostPort from the given slice that has 283 // the given space name associated. 284 func SelectHostsPortBySpaces(hps []HostPort, spaceNames ...SpaceName) ([]HostPort, bool) { 285 if len(spaceNames) == 0 { 286 logger.Errorf("host ports not filtered - no spaces given.") 287 return hps, false 288 } 289 290 var selectedHostPorts []HostPort 291 for _, hp := range hps { 292 if spaceNameList(spaceNames).IndexOf(hp.SpaceName) >= 0 { 293 logger.Debugf("selected %q as a hostPort in space %q", hp.Value, hp.SpaceName) 294 selectedHostPorts = append(selectedHostPorts, hp) 295 } 296 } 297 298 if len(selectedHostPorts) > 0 { 299 return selectedHostPorts, true 300 } 301 302 logger.Errorf("no hostPorts found in spaces %s", spaceNames) 303 return hps, false 304 } 305 306 // SelectControllerAddress returns the most suitable address to use as a Juju 307 // Controller (API/state server) endpoint given the list of addresses. 308 // The second return value is false when no address can be returned. 309 // When machineLocal is true both ScopeCloudLocal and ScopeMachineLocal 310 // addresses are considered during the selection, otherwise just ScopeCloudLocal are. 311 func SelectControllerAddress(addresses []Address, machineLocal bool) (Address, bool) { 312 internalAddress, ok := SelectInternalAddress(addresses, machineLocal) 313 logger.Debugf( 314 "selected %q as controller address, using scope %q", 315 internalAddress.Value, internalAddress.Scope, 316 ) 317 return internalAddress, ok 318 } 319 320 // SelectMongoHostPorts returns the most suitable HostPort (as string) to 321 // use as a Juju Controller (API/state server) endpoint given the list of 322 // hostPorts. It first tries to find the first HostPort bound to the 323 // spaces provided, then, if that fails, uses the older selection method based on scope. 324 // When machineLocal is true and an address can't be selected by space both 325 // ScopeCloudLocal and ScopeMachineLocal addresses are considered during the 326 // selection, otherwise just ScopeCloudLocal are. 327 func SelectMongoHostPortsBySpaces(hostPorts []HostPort, spaces []SpaceName) ([]string, bool) { 328 filteredHostPorts, ok := SelectHostsPortBySpaces(hostPorts, spaces...) 329 if ok { 330 logger.Debugf( 331 "selected %q as controller host:port, using spaces %q", 332 filteredHostPorts, spaces, 333 ) 334 } 335 return HostPortsToStrings(filteredHostPorts), ok 336 } 337 338 func SelectMongoHostPortsByScope(hostPorts []HostPort, machineLocal bool) []string { 339 // Fallback to using the legacy and error-prone approach using scope 340 // selection instead. 341 internalHP := SelectInternalHostPort(hostPorts, machineLocal) 342 logger.Debugf( 343 "selected %q as controller host:port, using scope selection", 344 internalHP, 345 ) 346 return []string{internalHP} 347 } 348 349 // SelectPublicAddress picks one address from a slice that would be 350 // appropriate to display as a publicly accessible endpoint. If there 351 // are no suitable addresses, then ok is false (and an empty address is 352 // returned). If a suitable address is then ok is true. 353 func SelectPublicAddress(addresses []Address) (Address, bool) { 354 index := bestAddressIndex(len(addresses), func(i int) Address { 355 return addresses[i] 356 }, publicMatch) 357 if index < 0 { 358 return Address{}, false 359 } 360 return addresses[index], true 361 } 362 363 // SelectPublicHostPort picks one HostPort from a slice that would be 364 // appropriate to display as a publicly accessible endpoint. If there 365 // are no suitable candidates, the empty string is returned. 366 func SelectPublicHostPort(hps []HostPort) string { 367 index := bestAddressIndex(len(hps), func(i int) Address { 368 return hps[i].Address 369 }, publicMatch) 370 if index < 0 { 371 return "" 372 } 373 return hps[index].NetAddr() 374 } 375 376 // SelectInternalAddress picks one address from a slice that can be 377 // used as an endpoint for juju internal communication. If there are 378 // are no suitable addresses, then ok is false (and an empty address is 379 // returned). If a suitable address was found then ok is true. 380 func SelectInternalAddress(addresses []Address, machineLocal bool) (Address, bool) { 381 index := bestAddressIndex(len(addresses), func(i int) Address { 382 return addresses[i] 383 }, internalAddressMatcher(machineLocal)) 384 if index < 0 { 385 return Address{}, false 386 } 387 return addresses[index], true 388 } 389 390 // SelectInternalHostPort picks one HostPort from a slice that can be 391 // used as an endpoint for juju internal communication and returns it 392 // in its NetAddr form. If there are no suitable addresses, the empty 393 // string is returned. 394 func SelectInternalHostPort(hps []HostPort, machineLocal bool) string { 395 index := bestAddressIndex(len(hps), func(i int) Address { 396 return hps[i].Address 397 }, internalAddressMatcher(machineLocal)) 398 if index < 0 { 399 return "" 400 } 401 return hps[index].NetAddr() 402 } 403 404 // SelectInternalHostPorts picks the best matching HostPorts from a 405 // slice that can be used as an endpoint for juju internal 406 // communication and returns them in NetAddr form. If there are no 407 // suitable addresses, an empty slice is returned. 408 func SelectInternalHostPorts(hps []HostPort, machineLocal bool) []string { 409 indexes := bestAddressIndexes(len(hps), func(i int) Address { 410 return hps[i].Address 411 }, internalAddressMatcher(machineLocal)) 412 413 out := make([]string, 0, len(indexes)) 414 for _, index := range indexes { 415 out = append(out, hps[index].NetAddr()) 416 } 417 return out 418 } 419 420 // PrioritizeInternalHostPorts orders the provided addresses by best 421 // match for use as an endpoint for juju internal communication and 422 // returns them in NetAddr form. If there are no suitable addresses 423 // then an empty slice is returned. 424 func PrioritizeInternalHostPorts(hps []HostPort, machineLocal bool) []string { 425 indexes := prioritizedAddressIndexes(len(hps), func(i int) Address { 426 return hps[i].Address 427 }, internalAddressMatcher(machineLocal)) 428 429 out := make([]string, 0, len(indexes)) 430 for _, index := range indexes { 431 out = append(out, hps[index].NetAddr()) 432 } 433 return out 434 } 435 436 func publicMatch(addr Address) scopeMatch { 437 switch addr.Scope { 438 case ScopePublic: 439 if addr.Type == IPv4Address { 440 return exactScopeIPv4 441 } 442 return exactScope 443 case ScopeCloudLocal, ScopeUnknown: 444 if addr.Type == IPv4Address { 445 return fallbackScopeIPv4 446 } 447 return fallbackScope 448 } 449 return invalidScope 450 } 451 452 func internalAddressMatcher(machineLocal bool) scopeMatchFunc { 453 if machineLocal { 454 return cloudOrMachineLocalMatch 455 } 456 return cloudLocalMatch 457 } 458 459 func cloudLocalMatch(addr Address) scopeMatch { 460 switch addr.Scope { 461 case ScopeCloudLocal: 462 if addr.Type == IPv4Address { 463 return exactScopeIPv4 464 } 465 return exactScope 466 case ScopePublic, ScopeUnknown: 467 if addr.Type == IPv4Address { 468 return fallbackScopeIPv4 469 } 470 return fallbackScope 471 } 472 return invalidScope 473 } 474 475 func cloudOrMachineLocalMatch(addr Address) scopeMatch { 476 if addr.Scope == ScopeMachineLocal { 477 if addr.Type == IPv4Address { 478 return exactScopeIPv4 479 } 480 return exactScope 481 } 482 return cloudLocalMatch(addr) 483 } 484 485 type scopeMatch int 486 487 const ( 488 invalidScope scopeMatch = iota 489 exactScopeIPv4 490 exactScope 491 fallbackScopeIPv4 492 fallbackScope 493 ) 494 495 type scopeMatchFunc func(addr Address) scopeMatch 496 497 type addressByIndexFunc func(index int) Address 498 499 // bestAddressIndex returns the index of the addresses with the best matching 500 // scope (according to the matchFunc). -1 is returned if there were no suitable 501 // addresses. 502 func bestAddressIndex(numAddr int, getAddrFunc addressByIndexFunc, matchFunc scopeMatchFunc) int { 503 indexes := bestAddressIndexes(numAddr, getAddrFunc, matchFunc) 504 if len(indexes) > 0 { 505 return indexes[0] 506 } 507 return -1 508 } 509 510 // bestAddressIndexes returns the indexes of the addresses with the best 511 // matching scope and type (according to the matchFunc). An empty slice is 512 // returned if there were no suitable addresses. 513 func bestAddressIndexes(numAddr int, getAddrFunc addressByIndexFunc, matchFunc scopeMatchFunc) []int { 514 // Categorise addresses by scope and type matching quality. 515 matches := filterAndCollateAddressIndexes(numAddr, getAddrFunc, matchFunc) 516 517 // Retrieve the indexes of the addresses with the best scope and type match. 518 allowedMatchTypes := []scopeMatch{exactScopeIPv4, exactScope, fallbackScopeIPv4, fallbackScope} 519 for _, matchType := range allowedMatchTypes { 520 indexes, ok := matches[matchType] 521 if ok && len(indexes) > 0 { 522 return indexes 523 } 524 } 525 return []int{} 526 } 527 528 func prioritizedAddressIndexes(numAddr int, getAddrFunc addressByIndexFunc, matchFunc scopeMatchFunc) []int { 529 // Categorise addresses by scope and type matching quality. 530 matches := filterAndCollateAddressIndexes(numAddr, getAddrFunc, matchFunc) 531 532 // Retrieve the indexes of the addresses with the best scope and type match. 533 allowedMatchTypes := []scopeMatch{exactScopeIPv4, exactScope, fallbackScopeIPv4, fallbackScope} 534 var prioritized []int 535 for _, matchType := range allowedMatchTypes { 536 indexes, ok := matches[matchType] 537 if ok && len(indexes) > 0 { 538 prioritized = append(prioritized, indexes...) 539 } 540 } 541 return prioritized 542 } 543 544 func filterAndCollateAddressIndexes(numAddr int, getAddrFunc addressByIndexFunc, matchFunc scopeMatchFunc) map[scopeMatch][]int { 545 // Categorise addresses by scope and type matching quality. 546 matches := make(map[scopeMatch][]int) 547 for i := 0; i < numAddr; i++ { 548 matchType := matchFunc(getAddrFunc(i)) 549 switch matchType { 550 case exactScopeIPv4, exactScope, fallbackScopeIPv4, fallbackScope: 551 matches[matchType] = append(matches[matchType], i) 552 } 553 } 554 return matches 555 } 556 557 // sortOrder calculates the "weight" of the address when sorting: 558 // - public IPs first; 559 // - hostnames after that, but "localhost" will be last if present; 560 // - cloud-local next; 561 // - machine-local next; 562 // - link-local next; 563 // - non-hostnames with unknown scope last. 564 func (a Address) sortOrder() int { 565 order := 0xFF 566 switch a.Scope { 567 case ScopePublic: 568 order = 0x00 569 case ScopeCloudLocal: 570 order = 0x20 571 case ScopeMachineLocal: 572 order = 0x40 573 case ScopeLinkLocal: 574 order = 0x80 575 } 576 switch a.Type { 577 case HostName: 578 order = 0x10 579 if a.Value == "localhost" { 580 order++ 581 } 582 case IPv6Address: 583 // Prefer IPv4 over IPv6 addresses. 584 order++ 585 case IPv4Address: 586 } 587 return order 588 } 589 590 type addressesPreferringIPv4Slice []Address 591 592 func (a addressesPreferringIPv4Slice) Len() int { return len(a) } 593 func (a addressesPreferringIPv4Slice) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 594 func (a addressesPreferringIPv4Slice) Less(i, j int) bool { 595 addr1 := a[i] 596 addr2 := a[j] 597 order1 := addr1.sortOrder() 598 order2 := addr2.sortOrder() 599 if order1 == order2 { 600 return addr1.Value < addr2.Value 601 } 602 return order1 < order2 603 } 604 605 // SortAddresses sorts the given Address slice according to the sortOrder of 606 // each address. See Address.sortOrder() for more info. 607 func SortAddresses(addrs []Address) { 608 sort.Sort(addressesPreferringIPv4Slice(addrs)) 609 } 610 611 // DecimalToIPv4 converts a decimal to the dotted quad IP address format. 612 func DecimalToIPv4(addr uint32) net.IP { 613 bytes := make([]byte, 4) 614 binary.BigEndian.PutUint32(bytes, addr) 615 return net.IP(bytes) 616 } 617 618 // IPv4ToDecimal converts a dotted quad IP address to its decimal equivalent. 619 func IPv4ToDecimal(ipv4Addr net.IP) (uint32, error) { 620 ip := ipv4Addr.To4() 621 if ip == nil { 622 return 0, errors.Errorf("%q is not a valid IPv4 address", ipv4Addr.String()) 623 } 624 return binary.BigEndian.Uint32([]byte(ip)), nil 625 } 626 627 // ResolvableHostnames returns the set of all DNS resolvable names 628 // from addrs. Note that 'localhost' is always considered resolvable 629 // because it can be used both as an IPv4 or IPv6 endpoint (e.g., in 630 // IPv6-only networks). 631 func ResolvableHostnames(addrs []Address) []Address { 632 resolveableAddrs := make([]Address, 0, len(addrs)) 633 for _, addr := range addrs { 634 if addr.Value == "localhost" || net.ParseIP(addr.Value) != nil { 635 resolveableAddrs = append(resolveableAddrs, addr) 636 continue 637 } 638 _, err := netLookupIP(addr.Value) 639 if err != nil { 640 logger.Infof("removing unresolvable address %q: %v", addr.Value, err) 641 continue 642 } 643 resolveableAddrs = append(resolveableAddrs, addr) 644 } 645 return resolveableAddrs 646 } 647 648 // MergedAddresses provides a single list of addresses without duplicates 649 // suitable for returning as an address list for a machine. 650 // TODO (cherylj) Add explicit unit tests - tracked with bug #1544158 651 func MergedAddresses(machineAddresses, providerAddresses []Address) []Address { 652 merged := make([]Address, 0, len(providerAddresses)+len(machineAddresses)) 653 providerValues := set.NewStrings() 654 for _, address := range providerAddresses { 655 // Older versions of Juju may have stored an empty address so ignore it here. 656 if address.Value == "" || providerValues.Contains(address.Value) { 657 continue 658 } 659 providerValues.Add(address.Value) 660 merged = append(merged, address) 661 } 662 for _, address := range machineAddresses { 663 if !providerValues.Contains(address.Value) { 664 merged = append(merged, address) 665 } 666 } 667 return merged 668 }