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  }