github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/core/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 "fmt" 9 "net" 10 "sort" 11 12 "github.com/juju/collections/set" 13 "github.com/juju/errors" 14 ) 15 16 // Private and special use network ranges for IPv4 and IPv6. 17 // See: http://tools.ietf.org/html/rfc1918 18 // Also: http://tools.ietf.org/html/rfc4193 19 // And: https://tools.ietf.org/html/rfc6890 20 var ( 21 classAPrivate = mustParseCIDR("10.0.0.0/8") 22 classBPrivate = mustParseCIDR("172.16.0.0/12") 23 classCPrivate = mustParseCIDR("192.168.0.0/16") 24 ipv6UniqueLocal = mustParseCIDR("fc00::/7") 25 classEReserved = mustParseCIDR("240.0.0.0/4") 26 ) 27 28 func mustParseCIDR(s string) *net.IPNet { 29 _, ipNet, err := net.ParseCIDR(s) 30 if err != nil { 31 panic(err) 32 } 33 return ipNet 34 } 35 36 // AddressConfigType defines valid network link configuration types. 37 // See interfaces(5) for details. 38 type AddressConfigType string 39 40 const ( 41 ConfigUnknown AddressConfigType = "" 42 ConfigDHCP AddressConfigType = "dhcp" 43 ConfigStatic AddressConfigType = "static" 44 ConfigManual AddressConfigType = "manual" 45 ConfigLoopback AddressConfigType = "loopback" 46 ) 47 48 // IsValidAddressConfigType returns whether the given value is a valid 49 // method to configure a link-layer network device's IP address. 50 // TODO (manadart 2021-05-04): There is an issue with the usage of this 51 // method in state where we have denormalised the config method so it is 52 // against device addresses. This is because "manual" indicates a device that 53 // has no configuration by default. This could never apply to an address. 54 func IsValidAddressConfigType(value string) bool { 55 switch AddressConfigType(value) { 56 case ConfigLoopback, ConfigStatic, ConfigDHCP, ConfigManual: 57 return true 58 } 59 return false 60 } 61 62 // AddressType represents the possible ways of specifying a machine location by 63 // either a hostname resolvable by dns lookup, or IPv4 or IPv6 address. 64 type AddressType string 65 66 const ( 67 HostName AddressType = "hostname" 68 IPv4Address AddressType = "ipv4" 69 IPv6Address AddressType = "ipv6" 70 ) 71 72 // Scope denotes the context a location may apply to. If a name or address can 73 // be reached from the wider internet, it is considered public. 74 // A private network address is either specific to the cloud or cloud subnet a 75 // machine belongs to, or to the machine itself for containers. 76 type Scope string 77 78 const ( 79 ScopeUnknown Scope = "" 80 ScopePublic Scope = "public" 81 ScopeCloudLocal Scope = "local-cloud" 82 ScopeFanLocal Scope = "local-fan" 83 ScopeMachineLocal Scope = "local-machine" 84 ScopeLinkLocal Scope = "link-local" 85 ) 86 87 // ScopeMatch is a numeric designation of how well the requirement 88 // for satisfying a scope is met. 89 type ScopeMatch int 90 91 const ( 92 invalidScope ScopeMatch = iota 93 exactScopeIPv4 94 exactScope 95 firstFallbackScopeIPv4 96 firstFallbackScope 97 secondFallbackScopeIPv4 98 secondFallbackScope 99 ) 100 101 // Address describes methods for returning details 102 // about an IP address or host name. 103 type Address interface { 104 // Host returns the value for the host-name/IP address. 105 Host() string 106 107 // AddressType returns the type of the address. 108 AddressType() AddressType 109 110 // AddressScope returns the scope of the address. 111 AddressScope() Scope 112 113 // AddressCIDR returns the subnet CIDR of the address. 114 AddressCIDR() string 115 116 // AddressConfigType returns the configuration method of the address. 117 AddressConfigType() AddressConfigType 118 119 // AddressIsSecondary returns whether this address is not the 120 // primary address associated with the network device. 121 AddressIsSecondary() bool 122 } 123 124 // ScopeMatchFunc is an alias for a function that accepts an Address, 125 // and returns what kind of scope match is determined by the body. 126 type ScopeMatchFunc = func(addr Address) ScopeMatch 127 128 // ExactScopeMatch checks if an address exactly 129 // matches any of the specified scopes. 130 func ExactScopeMatch(addr Address, addrScopes ...Scope) bool { 131 for _, scope := range addrScopes { 132 if addr.AddressScope() == scope { 133 return true 134 } 135 } 136 return false 137 } 138 139 // SortOrderMostPublic calculates the "weight" of the address to use when 140 // sorting such that the most accessible addresses will appear first: 141 // - public IPs first; 142 // - hostnames after that, but "localhost" will be last if present; 143 // - cloud-local next; 144 // - fan-local next; 145 // - machine-local next; 146 // - link-local next; 147 // - non-hostnames with unknown scope last. 148 // Secondary addresses with otherwise equal weight will be sorted to come after 149 // primary addresses, including host names *except* localhost. 150 func SortOrderMostPublic(a Address) int { 151 order := 100 152 153 switch a.AddressScope() { 154 case ScopePublic: 155 order = 0 156 // Special case to ensure that these follow non-localhost host names. 157 if a.AddressIsSecondary() { 158 order = 10 159 } 160 case ScopeCloudLocal: 161 order = 30 162 case ScopeFanLocal: 163 order = 50 164 case ScopeMachineLocal: 165 order = 70 166 case ScopeLinkLocal: 167 order = 90 168 } 169 170 switch a.AddressType() { 171 case HostName: 172 order = 10 173 if a.Host() == "localhost" { 174 order = 20 175 } 176 case IPv6Address: 177 order++ 178 case IPv4Address: 179 } 180 181 if a.AddressIsSecondary() { 182 order += 2 183 } 184 185 return order 186 } 187 188 // MachineAddress represents an address without associated space or provider 189 // information. Addresses of this form will be supplied by an agent running 190 // directly on a machine or container, or returned for requests where space 191 // information is irrelevant to usage. 192 type MachineAddress struct { 193 // Value is an IP address or hostname. 194 Value string 195 196 // Type indicates the form of the address value; 197 // IPv4, IPv6 or host-name. 198 Type AddressType 199 200 // Scope indicates the visibility of this address. 201 Scope Scope 202 203 // CIDR is used for IP addresses to indicate 204 // the subnet that they are part of. 205 CIDR string 206 207 // ConfigType denotes how this address was configured. 208 ConfigType AddressConfigType 209 210 // IsSecondary if true, indicates that this address is not the primary 211 // address associated with the network device. 212 IsSecondary bool 213 } 214 215 // Host returns the value for the host-name/IP address. 216 func (a MachineAddress) Host() string { 217 return a.Value 218 } 219 220 // AddressType returns the type of the address. 221 func (a MachineAddress) AddressType() AddressType { 222 return a.Type 223 } 224 225 // AddressScope returns the scope of the address. 226 func (a MachineAddress) AddressScope() Scope { 227 return a.Scope 228 } 229 230 // AddressCIDR returns the subnet CIDR of the address. 231 func (a MachineAddress) AddressCIDR() string { 232 return a.CIDR 233 } 234 235 // AddressConfigType returns the configuration method of the address. 236 func (a MachineAddress) AddressConfigType() AddressConfigType { 237 return a.ConfigType 238 } 239 240 // AddressIsSecondary returns whether this address is not the 241 // primary address associated with the network device. 242 func (a MachineAddress) AddressIsSecondary() bool { 243 return a.IsSecondary 244 } 245 246 // GoString implements fmt.GoStringer. 247 func (a MachineAddress) GoString() string { 248 return a.String() 249 } 250 251 // String returns the address value, prefixed with the scope if known. 252 func (a MachineAddress) String() string { 253 var prefix string 254 if a.Scope != ScopeUnknown { 255 prefix = string(a.Scope) + ":" 256 } 257 return prefix + a.Value 258 } 259 260 // IP returns the net.IP representation of this address. 261 func (a MachineAddress) IP() net.IP { 262 return net.ParseIP(a.Value) 263 } 264 265 // ValueWithMask returns the value of the address combined 266 // with the subnet mask indicated by its CIDR. 267 func (a MachineAddress) ValueWithMask() (string, error) { 268 // Returning a NotFound error preserves prior behaviour from when 269 // CIDRAddress was a method on InterfaceInfo. 270 // TODO (manadart 2021-03-16): Rethink this as we clean up InterfaceInfos 271 // and its corresponding wire type. 272 if a.Value == "" || a.CIDR == "" { 273 return "", errors.NotFoundf("address and CIDR pair (%q, %q)", a.Value, a.CIDR) 274 } 275 276 _, ipNet, err := net.ParseCIDR(a.CIDR) 277 if err != nil { 278 return "", errors.Trace(err) 279 } 280 281 ip := a.IP() 282 if ip == nil { 283 return "", errors.Errorf("cannot parse IP address %q", a.Value) 284 } 285 286 ipNet.IP = ip 287 return ipNet.String(), nil 288 } 289 290 // AsProviderAddress is used to construct a ProviderAddress 291 // from a MachineAddress 292 func (a MachineAddress) AsProviderAddress(options ...func(mutator ProviderAddressMutator)) ProviderAddress { 293 addr := ProviderAddress{MachineAddress: a} 294 295 for _, option := range options { 296 option(&addr) 297 } 298 299 return addr 300 } 301 302 // NewMachineAddress creates a new MachineAddress, 303 // applying any supplied options to the result. 304 func NewMachineAddress(value string, options ...func(AddressMutator)) MachineAddress { 305 addr := MachineAddress{ 306 Value: value, 307 Type: DeriveAddressType(value), 308 Scope: ScopeUnknown, 309 } 310 311 for _, option := range options { 312 option(&addr) 313 } 314 315 if addr.Scope == ScopeUnknown { 316 addr.Scope = deriveScope(addr) 317 } 318 319 return addr 320 } 321 322 // MachineAddresses is a slice of MachineAddress 323 type MachineAddresses []MachineAddress 324 325 // NewMachineAddresses is a convenience function to create addresses 326 // from a variable number of string arguments, applying any supplied 327 // options to each address 328 func NewMachineAddresses(values []string, options ...func(AddressMutator)) MachineAddresses { 329 if len(values) == 0 { 330 return nil 331 } 332 333 addrs := make(MachineAddresses, len(values)) 334 for i, value := range values { 335 addrs[i] = NewMachineAddress(value, options...) 336 } 337 return addrs 338 } 339 340 // AsProviderAddresses is used to construct ProviderAddresses 341 // element-wise from MachineAddresses 342 func (as MachineAddresses) AsProviderAddresses(options ...func(mutator ProviderAddressMutator)) ProviderAddresses { 343 if len(as) == 0 { 344 return nil 345 } 346 347 addrs := make(ProviderAddresses, len(as)) 348 for i, addr := range as { 349 addrs[i] = addr.AsProviderAddress(options...) 350 } 351 return addrs 352 } 353 354 // AllMatchingScope returns the addresses that satisfy 355 // the input scope matching function. 356 func (as MachineAddresses) AllMatchingScope(getMatcher ScopeMatchFunc) MachineAddresses { 357 return allMatchingScope(as, getMatcher) 358 } 359 360 // Values transforms the MachineAddresses to a string slice 361 // containing their raw IP values. 362 func (as MachineAddresses) Values() []string { 363 return toStrings(as) 364 } 365 366 // deriveScope attempts to derive the network scope from an address' 367 // type and value, returning the original network scope if no 368 // deduction can be made. 369 func deriveScope(addr MachineAddress) Scope { 370 if addr.Type == HostName { 371 return addr.Scope 372 } 373 ip := net.ParseIP(addr.Value) 374 if ip == nil { 375 return addr.Scope 376 } 377 if ip.IsLoopback() { 378 return ScopeMachineLocal 379 } 380 if isIPv4PrivateNetworkAddress(addr.Type, ip) || 381 isIPv6UniqueLocalAddress(addr.Type, ip) { 382 return ScopeCloudLocal 383 } 384 if isIPv4ReservedEAddress(addr.Type, ip) { 385 return ScopeFanLocal 386 } 387 388 if ip.IsLinkLocalMulticast() || 389 ip.IsLinkLocalUnicast() || 390 ip.IsInterfaceLocalMulticast() { 391 return ScopeLinkLocal 392 } 393 if ip.IsGlobalUnicast() { 394 return ScopePublic 395 } 396 return addr.Scope 397 } 398 399 func isIPv4PrivateNetworkAddress(addrType AddressType, ip net.IP) bool { 400 if addrType != IPv4Address { 401 return false 402 } 403 return classAPrivate.Contains(ip) || 404 classBPrivate.Contains(ip) || 405 classCPrivate.Contains(ip) 406 } 407 408 func isIPv4ReservedEAddress(addrType AddressType, ip net.IP) bool { 409 if addrType != IPv4Address { 410 return false 411 } 412 return classEReserved.Contains(ip) 413 } 414 415 func isIPv6UniqueLocalAddress(addrType AddressType, ip net.IP) bool { 416 if addrType != IPv6Address { 417 return false 418 } 419 return ipv6UniqueLocal.Contains(ip) 420 } 421 422 // InterfaceAddrs is patched for tests. 423 var InterfaceAddrs = func() ([]net.Addr, error) { 424 return net.InterfaceAddrs() 425 } 426 427 // IsLocalAddress returns true if the provided IP address equals to one of the 428 // local IP addresses. 429 func IsLocalAddress(ip net.IP) (bool, error) { 430 addrs, err := InterfaceAddrs() 431 if err != nil { 432 return false, errors.Trace(err) 433 } 434 435 for _, addr := range addrs { 436 localIP, _, err := net.ParseCIDR(addr.String()) 437 if err != nil { 438 continue 439 } 440 if localIP.To4() != nil || localIP.To16() != nil { 441 if ip.Equal(localIP) { 442 return true, nil 443 } 444 } 445 } 446 return false, nil 447 } 448 449 // ProviderAddress represents an address supplied by provider logic. 450 // It can include the provider's knowledge of the space in which the 451 // address resides. 452 type ProviderAddress struct { 453 MachineAddress 454 455 // SpaceName is the space in which this address resides 456 SpaceName SpaceName 457 458 // ProviderSpaceID is the provider's ID for the space this address is in 459 ProviderSpaceID Id 460 461 // ProviderID is the ID of this address's provider 462 ProviderID Id 463 464 // ProviderSubnetID is the provider's ID for the subnet this address is in 465 ProviderSubnetID Id 466 467 // ProviderVLANID is the provider's ID for the VLAN this address is in 468 ProviderVLANID Id 469 470 // VLANTag is the tag associated with this address's VLAN 471 VLANTag int 472 } 473 474 // GoString implements fmt.GoStringer. 475 func (a ProviderAddress) GoString() string { 476 return a.String() 477 } 478 479 // String returns a string representation of the address, in the form: 480 // `<scope>:<address-value>@<space-name>(id:<space-provider-id)`; for example: 481 // 482 // public:c2-54-226-162-124.compute-1.amazonaws.com@public-api(id:42) 483 // 484 // If the SpaceName is blank, the "@<space-name>" suffix will be omitted. 485 // Finally, if the ProviderSpaceID is empty the suffix 486 // "(id:<space-provider-id>)" part will be omitted as well. 487 func (a ProviderAddress) String() string { 488 var buf bytes.Buffer 489 buf.WriteString(a.MachineAddress.String()) 490 491 var spaceFound bool 492 if a.SpaceName != "" { 493 spaceFound = true 494 buf.WriteByte('@') 495 buf.WriteString(string(a.SpaceName)) 496 } 497 if a.ProviderSpaceID != "" { 498 if !spaceFound { 499 buf.WriteByte('@') 500 } 501 buf.WriteString(fmt.Sprintf("(id:%v)", string(a.ProviderSpaceID))) 502 } 503 504 return buf.String() 505 } 506 507 // ProviderAddresses is a slice of ProviderAddress 508 // supporting conversion to SpaceAddresses. 509 type ProviderAddresses []ProviderAddress 510 511 // Values transforms the ProviderAddresses to a string slice containing 512 // their raw IP values. 513 func (pas ProviderAddresses) Values() []string { 514 return toStrings(pas) 515 } 516 517 // ToSpaceAddresses transforms the ProviderAddresses to SpaceAddresses by using 518 // the input lookup to get a space ID from the name or the CIDR. 519 func (pas ProviderAddresses) ToSpaceAddresses(lookup SpaceLookup) (SpaceAddresses, error) { 520 if pas == nil { 521 return nil, nil 522 } 523 524 var spaceInfos SpaceInfos 525 if len(pas) > 0 { 526 var err error 527 if spaceInfos, err = lookup.AllSpaceInfos(); err != nil { 528 return nil, errors.Trace(err) 529 } 530 } 531 532 sas := make(SpaceAddresses, len(pas)) 533 for i, pa := range pas { 534 sas[i] = SpaceAddress{MachineAddress: pa.MachineAddress} 535 536 // If the provider explicitly sets the space, i.e. MAAS, prefer the name. 537 if pa.SpaceName != "" { 538 info := spaceInfos.GetByName(string(pa.SpaceName)) 539 if info == nil { 540 return nil, errors.NotFoundf("space with name %q", pa.SpaceName) 541 } 542 sas[i].SpaceID = info.ID 543 continue 544 } 545 546 // Otherwise attempt to look up the CIDR. 547 sInfo, err := spaceInfos.InferSpaceFromCIDRAndSubnetID(pa.CIDR, string(pa.ProviderSubnetID)) 548 if err != nil { 549 logger.Debugf("no matching subnet for CIDR %q and provider ID %q", pa.CIDR, pa.ProviderSubnetID) 550 continue 551 } 552 sas[i].SpaceID = sInfo.ID 553 } 554 return sas, nil 555 } 556 557 // OneMatchingScope returns the address that best satisfies the input scope 558 // matching function. The boolean return indicates if a match was found. 559 func (pas ProviderAddresses) OneMatchingScope(getMatcher ScopeMatchFunc) (ProviderAddress, bool) { 560 indexes := indexesForScope(pas, getMatcher) 561 if len(indexes) == 0 { 562 return ProviderAddress{}, false 563 } 564 addr := pas[indexes[0]] 565 logger.Debugf("selected %q as address, using scope %q", addr.Value, addr.Scope) 566 return addr, true 567 } 568 569 // SpaceAddress represents the location of a machine, including metadata 570 // about what kind of location the address describes. 571 // This is a server-side type that may include a space reference. 572 // It is used in logic for filtering addresses by space. 573 type SpaceAddress struct { 574 MachineAddress 575 SpaceID string 576 } 577 578 // GoString implements fmt.GoStringer. 579 func (a SpaceAddress) GoString() string { 580 return a.String() 581 } 582 583 // String returns a string representation of the address, in the form: 584 // `<scope>:<address-value>@space:<space-id>`; for example: 585 // 586 // public:c2-54-226-162-124.compute-1.amazonaws.com@space:1 587 // 588 // If the Space ID is empty, the @space:<space-id> suffix will be omitted. 589 func (a SpaceAddress) String() string { 590 var buf bytes.Buffer 591 buf.WriteString(a.MachineAddress.String()) 592 593 if a.SpaceID != "" { 594 buf.WriteString("@space:") 595 buf.WriteString(a.SpaceID) 596 } 597 598 return buf.String() 599 } 600 601 // NewSpaceAddress creates a new SpaceAddress, 602 // applying any supplied options to the result. 603 func NewSpaceAddress(value string, options ...func(mutator AddressMutator)) SpaceAddress { 604 return SpaceAddress{MachineAddress: NewMachineAddress(value, options...)} 605 } 606 607 // SpaceAddresses is a slice of SpaceAddress 608 // supporting conversion to ProviderAddresses. 609 type SpaceAddresses []SpaceAddress 610 611 // NewSpaceAddresses is a convenience function to create addresses 612 // from a variable number of string arguments. 613 func NewSpaceAddresses(inAddresses ...string) (outAddresses SpaceAddresses) { 614 outAddresses = make(SpaceAddresses, len(inAddresses)) 615 for i, address := range inAddresses { 616 outAddresses[i] = NewSpaceAddress(address) 617 } 618 return outAddresses 619 } 620 621 // Values returns a slice of strings containing the IP/host-name of each of 622 // the receiver addresses. 623 func (sas SpaceAddresses) Values() []string { 624 return toStrings(sas) 625 } 626 627 // ToProviderAddresses transforms the SpaceAddresses to ProviderAddresses by using 628 // the input lookup for conversion of space ID to space info. 629 func (sas SpaceAddresses) ToProviderAddresses(lookup SpaceLookup) (ProviderAddresses, error) { 630 if sas == nil { 631 return nil, nil 632 } 633 634 var spaces SpaceInfos 635 if len(sas) > 0 { 636 var err error 637 if spaces, err = lookup.AllSpaceInfos(); err != nil { 638 return nil, errors.Trace(err) 639 } 640 } 641 642 pas := make(ProviderAddresses, len(sas)) 643 for i, sa := range sas { 644 pas[i] = ProviderAddress{MachineAddress: sa.MachineAddress} 645 if sa.SpaceID != "" { 646 info := spaces.GetByID(sa.SpaceID) 647 if info == nil { 648 return nil, errors.NotFoundf("space with ID %q", sa.SpaceID) 649 } 650 pas[i].SpaceName = info.Name 651 pas[i].ProviderSpaceID = info.ProviderId 652 } 653 } 654 return pas, nil 655 } 656 657 // InSpaces returns the SpaceAddresses that are in the input spaces. 658 func (sas SpaceAddresses) InSpaces(spaces ...SpaceInfo) (SpaceAddresses, bool) { 659 if len(spaces) == 0 { 660 logger.Errorf("addresses not filtered - no spaces given.") 661 return sas, false 662 } 663 664 spaceInfos := SpaceInfos(spaces) 665 var selectedAddresses SpaceAddresses 666 for _, addr := range sas { 667 if space := spaceInfos.GetByID(addr.SpaceID); space != nil { 668 logger.Debugf("selected %q as an address in space %q", addr.Value, space.Name) 669 selectedAddresses = append(selectedAddresses, addr) 670 } 671 } 672 673 if len(selectedAddresses) > 0 { 674 return selectedAddresses, true 675 } 676 677 logger.Errorf("no addresses found in spaces %s", spaceInfos) 678 return sas, false 679 } 680 681 // OneMatchingScope returns the address that best satisfies the input scope 682 // matching function. The boolean return indicates if a match was found. 683 func (sas SpaceAddresses) OneMatchingScope(getMatcher ScopeMatchFunc) (SpaceAddress, bool) { 684 addrs := sas.AllMatchingScope(getMatcher) 685 if len(addrs) == 0 { 686 return SpaceAddress{}, false 687 } 688 return addrs[0], true 689 } 690 691 // AllMatchingScope returns the addresses that satisfy 692 // the input scope matching function. 693 func (sas SpaceAddresses) AllMatchingScope(getMatcher ScopeMatchFunc) SpaceAddresses { 694 return allMatchingScope(sas, getMatcher) 695 } 696 697 // EqualTo returns true if this set of SpaceAddresses is equal to other. 698 func (sas SpaceAddresses) EqualTo(other SpaceAddresses) bool { 699 if len(sas) != len(other) { 700 return false 701 } 702 703 sort.Sort(sas) 704 sort.Sort(other) 705 for i := 0; i < len(sas); i++ { 706 if sas[i].String() != other[i].String() { 707 return false 708 } 709 } 710 711 return true 712 } 713 714 func (sas SpaceAddresses) Len() int { return len(sas) } 715 func (sas SpaceAddresses) Swap(i, j int) { sas[i], sas[j] = sas[j], sas[i] } 716 func (sas SpaceAddresses) Less(i, j int) bool { 717 addr1 := sas[i] 718 addr2 := sas[j] 719 order1 := SortOrderMostPublic(addr1) 720 order2 := SortOrderMostPublic(addr2) 721 if order1 == order2 { 722 return addr1.Value < addr2.Value 723 } 724 return order1 < order2 725 } 726 727 // DeriveAddressType attempts to detect the type of address given. 728 func DeriveAddressType(value string) AddressType { 729 ip := net.ParseIP(value) 730 switch { 731 case ip == nil: 732 // TODO(gz): Check value is a valid hostname 733 return HostName 734 case ip.To4() != nil: 735 return IPv4Address 736 case ip.To16() != nil: 737 return IPv6Address 738 default: 739 panic("Unknown form of IP address") 740 } 741 } 742 743 // ScopeMatchPublic is an address scope matching function for determining the 744 // extent to which the input address' scope satisfies a requirement for public 745 // accessibility. 746 func ScopeMatchPublic(addr Address) ScopeMatch { 747 switch addr.AddressScope() { 748 case ScopePublic: 749 if addr.AddressType() == IPv4Address { 750 return exactScopeIPv4 751 } 752 return exactScope 753 case ScopeCloudLocal: 754 if addr.AddressType() == IPv4Address { 755 return firstFallbackScopeIPv4 756 } 757 return firstFallbackScope 758 case ScopeFanLocal, ScopeUnknown: 759 if addr.AddressType() == IPv4Address { 760 return secondFallbackScopeIPv4 761 } 762 return secondFallbackScope 763 } 764 return invalidScope 765 } 766 767 func ScopeMatchMachineOrCloudLocal(addr Address) ScopeMatch { 768 if addr.AddressScope() == ScopeMachineLocal { 769 if addr.AddressType() == IPv4Address { 770 return exactScopeIPv4 771 } 772 return exactScope 773 } 774 return ScopeMatchCloudLocal(addr) 775 } 776 777 // ScopeMatchCloudLocal is an address scope matching function for determining 778 // the extent to which the input address' scope satisfies a requirement for 779 // accessibility from within the local cloud. 780 // Machine-only addresses do not satisfy this matcher. 781 func ScopeMatchCloudLocal(addr Address) ScopeMatch { 782 switch addr.AddressScope() { 783 case ScopeCloudLocal: 784 if addr.AddressType() == IPv4Address { 785 return exactScopeIPv4 786 } 787 return exactScope 788 case ScopeFanLocal: 789 if addr.AddressType() == IPv4Address { 790 return firstFallbackScopeIPv4 791 } 792 return firstFallbackScope 793 case ScopePublic, ScopeUnknown: 794 if addr.AddressType() == IPv4Address { 795 return secondFallbackScopeIPv4 796 } 797 return secondFallbackScope 798 } 799 return invalidScope 800 } 801 802 // MergedAddresses provides a single list of addresses without duplicates 803 // suitable for returning as an address list for a machine. 804 // TODO (cherylj) Add explicit unit tests - tracked with bug #1544158 805 func MergedAddresses(machineAddresses, providerAddresses []SpaceAddress) []SpaceAddress { 806 merged := make([]SpaceAddress, 0, len(providerAddresses)+len(machineAddresses)) 807 providerValues := set.NewStrings() 808 for _, address := range providerAddresses { 809 // Older versions of Juju may have stored an empty address so ignore it here. 810 if address.Value == "" || providerValues.Contains(address.Value) { 811 continue 812 } 813 providerValues.Add(address.Value) 814 merged = append(merged, address) 815 } 816 for _, address := range machineAddresses { 817 if !providerValues.Contains(address.Value) { 818 merged = append(merged, address) 819 } 820 } 821 return merged 822 } 823 824 // CIDRAddressType returns back an AddressType to indicate whether the supplied 825 // CIDR corresponds to an IPV4 or IPV6 range. An error will be returned if a 826 // non-valid CIDR is provided. 827 // 828 // Caveat: if the provided CIDR corresponds to an IPV6 range with a 4in6 829 // prefix, the function will classify it as an IPV4 address. This is a known 830 // limitation of the go stdlib IP parsing code but it's not something that we 831 // are likely to encounter in the wild so there is no need to add extra logic 832 // to work around it. 833 func CIDRAddressType(cidr string) (AddressType, error) { 834 _, netIP, err := net.ParseCIDR(cidr) 835 if err != nil { 836 return "", err 837 } 838 839 if netIP.IP.To4() != nil { 840 return IPv4Address, nil 841 } 842 843 return IPv6Address, nil 844 } 845 846 // NetworkCIDRFromIPAndMask constructs a CIDR for a network by applying the 847 // provided netmask to the specified address (can be either a host or network 848 // address) and formatting the result as a CIDR. 849 // 850 // For example, passing 10.0.0.4 and a /24 mask yields 10.0.0.0/24. 851 func NetworkCIDRFromIPAndMask(ip net.IP, netmask net.IPMask) string { 852 if ip == nil || netmask == nil { 853 return "" 854 } 855 856 hostBits, _ := netmask.Size() 857 return fmt.Sprintf("%s/%d", ip.Mask(netmask), hostBits) 858 } 859 860 // SpaceAddressCandidate describes property methods required 861 // for conversion to sortable space addresses. 862 type SpaceAddressCandidate interface { 863 Value() string 864 ConfigMethod() AddressConfigType 865 SubnetCIDR() string 866 IsSecondary() bool 867 } 868 869 // ConvertToSpaceAddress returns a SpaceAddress representing the 870 // input candidate address, by using the input subnet lookup to 871 // associate the address with a space.. 872 func ConvertToSpaceAddress(addr SpaceAddressCandidate, lookup SubnetLookup) (SpaceAddress, error) { 873 subnets, err := lookup.AllSubnetInfos() 874 if err != nil { 875 return SpaceAddress{}, errors.Trace(err) 876 } 877 878 cidr := addr.SubnetCIDR() 879 880 spaceAddr := SpaceAddress{ 881 MachineAddress: NewMachineAddress( 882 addr.Value(), 883 WithCIDR(cidr), 884 WithConfigType(addr.ConfigMethod()), 885 WithSecondary(addr.IsSecondary()), 886 ), 887 } 888 889 // Attempt to set the space ID based on the subnet. 890 if cidr != "" { 891 allMatching, err := subnets.GetByCIDR(cidr) 892 if err != nil { 893 return SpaceAddress{}, errors.Trace(err) 894 } 895 896 // This only holds true while CIDRs uniquely identify subnets. 897 if len(allMatching) != 0 { 898 spaceAddr.SpaceID = allMatching[0].SpaceID 899 } 900 } 901 902 return spaceAddr, nil 903 } 904 905 // noAddress represents an error when an address is requested but not available. 906 type noAddress struct { 907 errors.Err 908 } 909 910 // NoAddressError returns an error which satisfies IsNoAddressError(). The given 911 // addressKind specifies what kind of address(es) is(are) missing, usually 912 // "private" or "public". 913 func NoAddressError(addressKind string) error { 914 newErr := errors.NewErr("no %s address(es)", addressKind) 915 newErr.SetLocation(1) 916 return &noAddress{newErr} 917 } 918 919 // IsNoAddressError reports whether err was created with NoAddressError(). 920 func IsNoAddressError(err error) bool { 921 err = errors.Cause(err) 922 _, ok := err.(*noAddress) 923 return ok 924 } 925 926 // toStrings returns the IP addresses in string form for input 927 // that is a slice of types implementing the Address interface. 928 func toStrings[T Address](addrs []T) []string { 929 if addrs == nil { 930 return nil 931 } 932 933 ips := make([]string, len(addrs)) 934 for i, addr := range addrs { 935 ips[i] = addr.Host() 936 } 937 return ips 938 } 939 940 func allMatchingScope[T Address](addrs []T, getMatcher ScopeMatchFunc) []T { 941 indexes := indexesForScope(addrs, getMatcher) 942 if len(indexes) == 0 { 943 return nil 944 } 945 out := make([]T, len(indexes)) 946 for i, index := range indexes { 947 out[i] = addrs[index] 948 } 949 return out 950 } 951 952 // indexesForScope returns the indexes of the addresses with the best 953 // matching scope and type (according to the matchFunc). 954 // An empty slice is returned if there were no suitable addresses. 955 func indexesForScope[T Address](addrs []T, matchFunc ScopeMatchFunc) []int { 956 matches := filterAndCollateAddressIndexes(addrs, matchFunc) 957 958 for _, matchType := range scopeMatchHierarchy() { 959 indexes, ok := matches[matchType] 960 if ok && len(indexes) > 0 { 961 return indexes 962 } 963 } 964 return nil 965 } 966 967 // indexesByScopeMatch filters address indexes by matching scope, 968 // then returns them in descending order of best match. 969 func indexesByScopeMatch[T Address](addrs []T, matchFunc ScopeMatchFunc) []int { 970 matches := filterAndCollateAddressIndexes(addrs, matchFunc) 971 972 var prioritized []int 973 for _, matchType := range scopeMatchHierarchy() { 974 indexes, ok := matches[matchType] 975 if ok && len(indexes) > 0 { 976 prioritized = append(prioritized, indexes...) 977 } 978 } 979 return prioritized 980 } 981 982 // filterAndCollateAddressIndexes filters address indexes using the input scope 983 // matching function, then returns the results grouped by scope match quality. 984 // Invalid results are omitted. 985 func filterAndCollateAddressIndexes[T Address](addrs []T, matchFunc ScopeMatchFunc) map[ScopeMatch][]int { 986 matches := make(map[ScopeMatch][]int) 987 for i, addr := range addrs { 988 matchType := matchFunc(addr) 989 if matchType != invalidScope { 990 matches[matchType] = append(matches[matchType], i) 991 } 992 } 993 return matches 994 } 995 996 func scopeMatchHierarchy() []ScopeMatch { 997 return []ScopeMatch{ 998 exactScopeIPv4, exactScope, 999 firstFallbackScopeIPv4, firstFallbackScope, 1000 secondFallbackScopeIPv4, secondFallbackScope, 1001 } 1002 }