github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/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  	"net"
    10  	"sort"
    11  
    12  	"github.com/juju/errors"
    13  )
    14  
    15  // Private network ranges for IPv4 and IPv6.
    16  // See: http://tools.ietf.org/html/rfc1918
    17  // Also: http://tools.ietf.org/html/rfc4193
    18  var (
    19  	classAPrivate   = mustParseCIDR("10.0.0.0/8")
    20  	classBPrivate   = mustParseCIDR("172.16.0.0/12")
    21  	classCPrivate   = mustParseCIDR("192.168.0.0/16")
    22  	ipv6UniqueLocal = mustParseCIDR("fc00::/7")
    23  )
    24  
    25  // globalPreferIPv6 determines whether IPv6 addresses will be
    26  // preferred when selecting a public or internal addresses, using the
    27  // Select*() methods below. InitializeFromConfig() needs to be called
    28  // to set this flag globally at the earliest time possible (e.g. at
    29  // bootstrap, agent startup, before any CLI command).
    30  var globalPreferIPv6 bool = false
    31  
    32  // ResetGobalPreferIPv6 resets the global variable back to the default,
    33  // and is called only from the isolation test suite to make sure we have
    34  // a clean environment.
    35  func ResetGobalPreferIPv6() {
    36  	globalPreferIPv6 = false
    37  }
    38  
    39  func mustParseCIDR(s string) *net.IPNet {
    40  	_, net, err := net.ParseCIDR(s)
    41  	if err != nil {
    42  		panic(err)
    43  	}
    44  	return net
    45  }
    46  
    47  // AddressType represents the possible ways of specifying a machine location by
    48  // either a hostname resolvable by dns lookup, or IPv4 or IPv6 address.
    49  type AddressType string
    50  
    51  const (
    52  	HostName    AddressType = "hostname"
    53  	IPv4Address AddressType = "ipv4"
    54  	IPv6Address AddressType = "ipv6"
    55  )
    56  
    57  // Scope denotes the context a location may apply to. If a name or
    58  // address can be reached from the wider internet, it is considered
    59  // public. A private network address is either specific to the cloud
    60  // or cloud subnet a machine belongs to, or to the machine itself for
    61  // containers.
    62  type Scope string
    63  
    64  const (
    65  	ScopeUnknown      Scope = ""
    66  	ScopePublic       Scope = "public"
    67  	ScopeCloudLocal   Scope = "local-cloud"
    68  	ScopeMachineLocal Scope = "local-machine"
    69  	ScopeLinkLocal    Scope = "link-local"
    70  )
    71  
    72  // Address represents the location of a machine, including metadata
    73  // about what kind of location the address describes.
    74  type Address struct {
    75  	Value       string
    76  	Type        AddressType
    77  	NetworkName string
    78  	Scope
    79  }
    80  
    81  // String returns a string representation of the address,
    82  // in the form: scope:address(network name);
    83  // for example:
    84  //
    85  //	public:c2-54-226-162-124.compute-1.amazonaws.com(ec2network)
    86  //
    87  // If the scope is NetworkUnknown, the initial scope: prefix will
    88  // be omitted. If the NetworkName is blank, the (network name) suffix
    89  // will be omitted.
    90  func (a Address) String() string {
    91  	var buf bytes.Buffer
    92  	if a.Scope != ScopeUnknown {
    93  		buf.WriteString(string(a.Scope))
    94  		buf.WriteByte(':')
    95  	}
    96  	buf.WriteString(a.Value)
    97  	if a.NetworkName != "" {
    98  		buf.WriteByte('(')
    99  		buf.WriteString(a.NetworkName)
   100  		buf.WriteByte(')')
   101  	}
   102  	return buf.String()
   103  }
   104  
   105  // GoString implements fmt.GoStringer.
   106  func (a Address) GoString() string {
   107  	return a.String()
   108  }
   109  
   110  // NewAddresses is a convenience function to create addresses from a string slice
   111  func NewAddresses(inAddresses ...string) (outAddresses []Address) {
   112  	for _, address := range inAddresses {
   113  		outAddresses = append(outAddresses, NewAddress(address, ScopeUnknown))
   114  	}
   115  	return outAddresses
   116  }
   117  
   118  // DeriveAddressType attempts to detect the type of address given.
   119  func DeriveAddressType(value string) AddressType {
   120  	ip := net.ParseIP(value)
   121  	switch {
   122  	case ip == nil:
   123  		// TODO(gz): Check value is a valid hostname
   124  		return HostName
   125  	case ip.To4() != nil:
   126  		return IPv4Address
   127  	case ip.To16() != nil:
   128  		return IPv6Address
   129  	default:
   130  		panic("Unknown form of IP address")
   131  	}
   132  }
   133  
   134  func isIPv4PrivateNetworkAddress(addrType AddressType, ip net.IP) bool {
   135  	if addrType != IPv4Address {
   136  		return false
   137  	}
   138  	return classAPrivate.Contains(ip) ||
   139  		classBPrivate.Contains(ip) ||
   140  		classCPrivate.Contains(ip)
   141  }
   142  
   143  func isIPv6UniqueLocalAddress(addrType AddressType, ip net.IP) bool {
   144  	if addrType != IPv6Address {
   145  		return false
   146  	}
   147  	return ipv6UniqueLocal.Contains(ip)
   148  }
   149  
   150  // deriveScope attempts to derive the network scope from an address's
   151  // type and value, returning the original network scope if no
   152  // deduction can be made.
   153  func deriveScope(addr Address) Scope {
   154  	if addr.Type == HostName {
   155  		return addr.Scope
   156  	}
   157  	ip := net.ParseIP(addr.Value)
   158  	if ip == nil {
   159  		return addr.Scope
   160  	}
   161  	if ip.IsLoopback() {
   162  		return ScopeMachineLocal
   163  	}
   164  	if isIPv4PrivateNetworkAddress(addr.Type, ip) ||
   165  		isIPv6UniqueLocalAddress(addr.Type, ip) {
   166  		return ScopeCloudLocal
   167  	}
   168  	if ip.IsLinkLocalMulticast() ||
   169  		ip.IsLinkLocalUnicast() ||
   170  		ip.IsInterfaceLocalMulticast() {
   171  		return ScopeLinkLocal
   172  	}
   173  	if ip.IsGlobalUnicast() {
   174  		return ScopePublic
   175  	}
   176  	return addr.Scope
   177  }
   178  
   179  // NewAddress creates a new Address, deriving its type from the value.
   180  //
   181  // If the specified scope is ScopeUnknown, then NewAddress will
   182  // attempt derive the scope based on reserved IP address ranges.
   183  func NewAddress(value string, scope Scope) Address {
   184  	addr := Address{
   185  		Value: value,
   186  		Type:  DeriveAddressType(value),
   187  		Scope: scope,
   188  	}
   189  	if scope == ScopeUnknown {
   190  		addr.Scope = deriveScope(addr)
   191  	}
   192  	return addr
   193  }
   194  
   195  // SelectPublicAddress picks one address from a slice that would be
   196  // appropriate to display as a publicly accessible endpoint. If there
   197  // are no suitable addresses, the empty string is returned.
   198  func SelectPublicAddress(addresses []Address) string {
   199  	index := bestAddressIndex(len(addresses), globalPreferIPv6, func(i int) Address {
   200  		return addresses[i]
   201  	}, publicMatch)
   202  	if index < 0 {
   203  		return ""
   204  	}
   205  	return addresses[index].Value
   206  }
   207  
   208  // SelectPublicHostPort picks one HostPort from a slice that would be
   209  // appropriate to display as a publicly accessible endpoint. If there
   210  // are no suitable candidates, the empty string is returned.
   211  func SelectPublicHostPort(hps []HostPort) string {
   212  	index := bestAddressIndex(len(hps), globalPreferIPv6, func(i int) Address {
   213  		return hps[i].Address
   214  	}, publicMatch)
   215  	if index < 0 {
   216  		return ""
   217  	}
   218  	return hps[index].NetAddr()
   219  }
   220  
   221  // SelectInternalAddress picks one address from a slice that can be
   222  // used as an endpoint for juju internal communication. If there are
   223  // no suitable addresses, the empty string is returned.
   224  func SelectInternalAddress(addresses []Address, machineLocal bool) string {
   225  	index := bestAddressIndex(len(addresses), globalPreferIPv6, func(i int) Address {
   226  		return addresses[i]
   227  	}, internalAddressMatcher(machineLocal))
   228  	if index < 0 {
   229  		return ""
   230  	}
   231  	return addresses[index].Value
   232  }
   233  
   234  // SelectInternalHostPort picks one HostPort from a slice that can be
   235  // used as an endpoint for juju internal communication and returns it
   236  // in its NetAddr form. If there are no suitable addresses, the empty
   237  // string is returned.
   238  func SelectInternalHostPort(hps []HostPort, machineLocal bool) string {
   239  	index := bestAddressIndex(len(hps), globalPreferIPv6, func(i int) Address {
   240  		return hps[i].Address
   241  	}, internalAddressMatcher(machineLocal))
   242  	if index < 0 {
   243  		return ""
   244  	}
   245  	return hps[index].NetAddr()
   246  }
   247  
   248  func publicMatch(addr Address, preferIPv6 bool) scopeMatch {
   249  	switch addr.Scope {
   250  	case ScopePublic:
   251  		return mayPreferIPv6(addr, exactScope, preferIPv6)
   252  	case ScopeCloudLocal, ScopeUnknown:
   253  		return mayPreferIPv6(addr, fallbackScope, preferIPv6)
   254  	}
   255  	return invalidScope
   256  }
   257  
   258  // mayPreferIPv6 returns mismatchedTypeExactScope or
   259  // mismatchedTypeFallbackScope (depending on originalScope) if addr's
   260  // type is IPv4, and preferIPv6 is true. When preferIPv6 is false, or
   261  // addr's type is IPv6 and preferIPv6 is true, returns the
   262  // originalScope unchanged.
   263  func mayPreferIPv6(addr Address, originalScope scopeMatch, preferIPv6 bool) scopeMatch {
   264  	if preferIPv6 && addr.Type != IPv6Address {
   265  		switch originalScope {
   266  		case exactScope:
   267  			return mismatchedTypeExactScope
   268  		case fallbackScope:
   269  			return mismatchedTypeFallbackScope
   270  		}
   271  		return invalidScope
   272  	}
   273  	return originalScope
   274  }
   275  
   276  func internalAddressMatcher(machineLocal bool) func(Address, bool) scopeMatch {
   277  	if machineLocal {
   278  		return cloudOrMachineLocalMatch
   279  	}
   280  	return cloudLocalMatch
   281  }
   282  
   283  func cloudLocalMatch(addr Address, preferIPv6 bool) scopeMatch {
   284  	switch addr.Scope {
   285  	case ScopeCloudLocal:
   286  		return mayPreferIPv6(addr, exactScope, preferIPv6)
   287  	case ScopePublic, ScopeUnknown:
   288  		return mayPreferIPv6(addr, fallbackScope, preferIPv6)
   289  	}
   290  	return invalidScope
   291  }
   292  
   293  func cloudOrMachineLocalMatch(addr Address, preferIPv6 bool) scopeMatch {
   294  	if addr.Scope == ScopeMachineLocal {
   295  		return mayPreferIPv6(addr, exactScope, preferIPv6)
   296  	}
   297  	return cloudLocalMatch(addr, preferIPv6)
   298  }
   299  
   300  type scopeMatch int
   301  
   302  const (
   303  	invalidScope scopeMatch = iota
   304  	exactScope
   305  	fallbackScope
   306  	mismatchedTypeExactScope
   307  	mismatchedTypeFallbackScope
   308  )
   309  
   310  // bestAddressIndex returns the index of the first address
   311  // with an exactly matching scope, or the first address with
   312  // a matching fallback scope if there are no exact matches, or
   313  // a matching scope but mismatched type when preferIPv6 is true.
   314  // If there are no suitable addresses, -1 is returned.
   315  func bestAddressIndex(numAddr int, preferIPv6 bool, getAddr func(i int) Address, match func(addr Address, preferIPv6 bool) scopeMatch) int {
   316  	fallbackAddressIndex := -1
   317  	mismatchedTypeFallbackIndex := -1
   318  	mismatchedTypeExactIndex := -1
   319  	for i := 0; i < numAddr; i++ {
   320  		addr := getAddr(i)
   321  		switch match(addr, preferIPv6) {
   322  		case exactScope:
   323  			logger.Tracef("exactScope match: index=%d,fallback=%d,mismatchedExact=%d,mismatchedFallback=%d,preferIPv6=%v", i, fallbackAddressIndex, mismatchedTypeExactIndex, mismatchedTypeFallbackIndex, preferIPv6)
   324  			return i
   325  		case fallbackScope:
   326  			logger.Tracef("fallbackScope match: index=%d,fallback=%d,mismatchedExact=%d,mismatchedFallback=%d,preferIPv6=%v", i, fallbackAddressIndex, mismatchedTypeExactIndex, mismatchedTypeFallbackIndex, preferIPv6)
   327  			// Use the first fallback address if there are no exact matches.
   328  			if fallbackAddressIndex == -1 {
   329  				fallbackAddressIndex = i
   330  			}
   331  		case mismatchedTypeExactScope:
   332  			logger.Tracef("mismatchedTypeExactScope match: index=%d,fallback=%d,mismatchedExact=%d,mismatchedFallback=%d,preferIPv6=%v", i, fallbackAddressIndex, mismatchedTypeExactIndex, mismatchedTypeFallbackIndex, preferIPv6)
   333  			// We have an exact scope match, but the type does not
   334  			// match, so save the first index as this is the best
   335  			// match so far.
   336  			if mismatchedTypeExactIndex == -1 {
   337  				mismatchedTypeExactIndex = i
   338  			}
   339  		case mismatchedTypeFallbackScope:
   340  			logger.Tracef("mismatchedTypeFallbackScope match: index=%d,fallback=%d,mismatchedExact=%d,mismatchedFallback=%d,preferIPv6=%v", i, fallbackAddressIndex, mismatchedTypeExactIndex, mismatchedTypeFallbackIndex, preferIPv6)
   341  			// We have a fallback scope match, but the type does not
   342  			// match, so we save the first index in case this is the
   343  			// best match so far.
   344  			if mismatchedTypeFallbackIndex == -1 {
   345  				mismatchedTypeFallbackIndex = i
   346  			}
   347  		}
   348  	}
   349  	if preferIPv6 {
   350  		if fallbackAddressIndex != -1 {
   351  			// Prefer an IPv6 fallback to a IPv4 mismatch.
   352  			logger.Tracef("fallbackScope return: index=%d,fallback=%d,mismatchedExact=%d,mismatchedFallback=%d,preferIPv6=%v", fallbackAddressIndex, fallbackAddressIndex, mismatchedTypeExactIndex, mismatchedTypeFallbackIndex, preferIPv6)
   353  			return fallbackAddressIndex
   354  		}
   355  		if mismatchedTypeExactIndex != -1 {
   356  			// Prefer an exact IPv4 match to a fallback.
   357  			logger.Tracef("mismatchedTypeExactScope return: index=%d,fallback=%d,mismatchedExact=%d,mismatchedFallback=%d,preferIPv6=%v", mismatchedTypeExactIndex, fallbackAddressIndex, mismatchedTypeExactIndex, mismatchedTypeFallbackIndex, preferIPv6)
   358  			return mismatchedTypeExactIndex
   359  		}
   360  		logger.Tracef("mismatchedTypeFallbackScope return: index=%d,fallback=%d,mismatchedExact=%d,mismatchedFallback=%d,preferIPv6=%v", mismatchedTypeFallbackIndex, fallbackAddressIndex, mismatchedTypeExactIndex, mismatchedTypeFallbackIndex, preferIPv6)
   361  		return mismatchedTypeFallbackIndex
   362  	}
   363  	logger.Tracef("fallbackScope return: index=%d,fallback=%d,mismatchedExact=%d,mismatchedFallback=%d,preferIPv6=%v", fallbackAddressIndex, fallbackAddressIndex, mismatchedTypeExactIndex, mismatchedTypeFallbackIndex, preferIPv6)
   364  	return fallbackAddressIndex
   365  }
   366  
   367  // sortOrder calculates the "weight" of the address when sorting,
   368  // taking into account the preferIPv6 flag:
   369  // - public IPs first;
   370  // - hostnames after that, but "localhost" will be last if present;
   371  // - cloud-local next;
   372  // - machine-local next;
   373  // - link-local next;
   374  // - non-hostnames with unknown scope last.
   375  //
   376  // When preferIPv6 flag and the address type do not match, the order
   377  // is incremented to put non-preferred addresses after preferred.
   378  func (a Address) sortOrder(preferIPv6 bool) int {
   379  	order := 0xFF
   380  	switch a.Scope {
   381  	case ScopePublic:
   382  		order = 0x00
   383  	case ScopeCloudLocal:
   384  		order = 0x20
   385  	case ScopeMachineLocal:
   386  		order = 0x40
   387  	case ScopeLinkLocal:
   388  		order = 0x80
   389  	}
   390  	switch a.Type {
   391  	case HostName:
   392  		order = 0x10
   393  		if a.Value == "localhost" {
   394  			order++
   395  		}
   396  	case IPv4Address:
   397  		if preferIPv6 {
   398  			order++
   399  		}
   400  	case IPv6Address:
   401  		if !preferIPv6 {
   402  			order++
   403  		}
   404  	}
   405  	return order
   406  }
   407  
   408  type addressesPreferringIPv4Slice []Address
   409  
   410  func (a addressesPreferringIPv4Slice) Len() int      { return len(a) }
   411  func (a addressesPreferringIPv4Slice) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
   412  func (a addressesPreferringIPv4Slice) Less(i, j int) bool {
   413  	addr1 := a[i]
   414  	addr2 := a[j]
   415  	order1 := addr1.sortOrder(false)
   416  	order2 := addr2.sortOrder(false)
   417  	if order1 == order2 {
   418  		return addr1.Value < addr2.Value
   419  	}
   420  	return order1 < order2
   421  }
   422  
   423  type addressesPreferringIPv6Slice struct {
   424  	addressesPreferringIPv4Slice
   425  }
   426  
   427  func (a addressesPreferringIPv6Slice) Less(i, j int) bool {
   428  	addr1 := a.addressesPreferringIPv4Slice[i]
   429  	addr2 := a.addressesPreferringIPv4Slice[j]
   430  	order1 := addr1.sortOrder(true)
   431  	order2 := addr2.sortOrder(true)
   432  	if order1 == order2 {
   433  		return addr1.Value < addr2.Value
   434  	}
   435  	return order1 < order2
   436  }
   437  
   438  // SortAddresses sorts the given Address slice according to the
   439  // sortOrder of each address and the preferIpv6 flag. See
   440  // Address.sortOrder() for more info.
   441  func SortAddresses(addrs []Address, preferIPv6 bool) {
   442  	if preferIPv6 {
   443  		sort.Sort(addressesPreferringIPv6Slice{addressesPreferringIPv4Slice(addrs)})
   444  	} else {
   445  		sort.Sort(addressesPreferringIPv4Slice(addrs))
   446  	}
   447  }
   448  
   449  // DecimalToIPv4 converts a decimal to the dotted quad IP address format.
   450  func DecimalToIPv4(addr uint32) net.IP {
   451  	bytes := make([]byte, 4)
   452  	binary.BigEndian.PutUint32(bytes, addr)
   453  	return net.IP(bytes)
   454  }
   455  
   456  // IPv4ToDecimal converts a dotted quad IP address to its decimal equivalent.
   457  func IPv4ToDecimal(ipv4Addr net.IP) (uint32, error) {
   458  	ip := ipv4Addr.To4()
   459  	if ip == nil {
   460  		return 0, errors.Errorf("%q is not a valid IPv4 address", ipv4Addr.String())
   461  	}
   462  	return binary.BigEndian.Uint32([]byte(ip)), nil
   463  }