github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/network/hostport.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package network
     5  
     6  import (
     7  	"net"
     8  	"sort"
     9  	"strconv"
    10  
    11  	"github.com/juju/errors"
    12  	"github.com/juju/utils/set"
    13  )
    14  
    15  // HostPort associates an address with a port.
    16  type HostPort struct {
    17  	Address
    18  	Port int
    19  }
    20  
    21  // NetAddr returns the host-port as an address
    22  // suitable for calling net.Dial.
    23  func (hp HostPort) NetAddr() string {
    24  	return net.JoinHostPort(hp.Value, strconv.Itoa(hp.Port))
    25  }
    26  
    27  // String implements Stringer.
    28  func (hp HostPort) String() string {
    29  	return hp.NetAddr()
    30  }
    31  
    32  // GoString implements fmt.GoStringer.
    33  func (hp HostPort) GoString() string {
    34  	return hp.String()
    35  }
    36  
    37  // AddressesWithPort returns the given addresses all
    38  // associated with the given port.
    39  func AddressesWithPort(addrs []Address, port int) []HostPort {
    40  	hps := make([]HostPort, len(addrs))
    41  	for i, addr := range addrs {
    42  		hps[i] = HostPort{
    43  			Address: addr,
    44  			Port:    port,
    45  		}
    46  	}
    47  	return hps
    48  }
    49  
    50  // NewHostPorts creates a list of HostPorts from each given string
    51  // address and port.
    52  func NewHostPorts(port int, addresses ...string) []HostPort {
    53  	hps := make([]HostPort, len(addresses))
    54  	for i, addr := range addresses {
    55  		hps[i] = HostPort{
    56  			Address: NewAddress(addr),
    57  			Port:    port,
    58  		}
    59  	}
    60  	return hps
    61  }
    62  
    63  // ParseHostPorts creates a list of HostPorts parsing each given
    64  // string containing address:port. An error is returned if any string
    65  // cannot be parsed as HostPort.
    66  func ParseHostPorts(hostPorts ...string) ([]HostPort, error) {
    67  	hps := make([]HostPort, len(hostPorts))
    68  	for i, hp := range hostPorts {
    69  		hostport, err := ParseHostPort(hp)
    70  		if err != nil {
    71  			return nil, errors.Trace(err)
    72  		}
    73  		hps[i] = *hostport
    74  	}
    75  	return hps, nil
    76  }
    77  
    78  // ParseHostPort converts a string containing a single host and port
    79  // value to a HostPort.
    80  func ParseHostPort(hp string) (*HostPort, error) {
    81  	host, port, err := net.SplitHostPort(hp)
    82  	if err != nil {
    83  		return nil, errors.Annotatef(err, "cannot parse %q as address:port", hp)
    84  	}
    85  	numPort, err := strconv.Atoi(port)
    86  	if err != nil {
    87  		return nil, errors.Annotatef(err, "cannot parse %q port", hp)
    88  	}
    89  	return &HostPort{
    90  		Address: NewAddress(host),
    91  		Port:    numPort,
    92  	}, nil
    93  }
    94  
    95  // HostsWithoutPort strips the port from each HostPort, returning just
    96  // the addresses.
    97  func HostsWithoutPort(hps []HostPort) []Address {
    98  	addrs := make([]Address, len(hps))
    99  	for i, hp := range hps {
   100  		addrs[i] = hp.Address
   101  	}
   102  	return addrs
   103  }
   104  
   105  type hostPortsPreferringIPv4Slice []HostPort
   106  
   107  func (hp hostPortsPreferringIPv4Slice) Len() int      { return len(hp) }
   108  func (hp hostPortsPreferringIPv4Slice) Swap(i, j int) { hp[i], hp[j] = hp[j], hp[i] }
   109  func (hp hostPortsPreferringIPv4Slice) Less(i, j int) bool {
   110  	hp1 := hp[i]
   111  	hp2 := hp[j]
   112  	order1 := hp1.sortOrder(false)
   113  	order2 := hp2.sortOrder(false)
   114  	if order1 == order2 {
   115  		if hp1.Address.Value == hp2.Address.Value {
   116  			return hp1.Port < hp2.Port
   117  		}
   118  		return hp1.Address.Value < hp2.Address.Value
   119  	}
   120  	return order1 < order2
   121  }
   122  
   123  type hostPortsPreferringIPv6Slice struct {
   124  	hostPortsPreferringIPv4Slice
   125  }
   126  
   127  func (hp hostPortsPreferringIPv6Slice) Less(i, j int) bool {
   128  	hp1 := hp.hostPortsPreferringIPv4Slice[i]
   129  	hp2 := hp.hostPortsPreferringIPv4Slice[j]
   130  	order1 := hp1.sortOrder(true)
   131  	order2 := hp2.sortOrder(true)
   132  	if order1 == order2 {
   133  		if hp1.Address.Value == hp2.Address.Value {
   134  			return hp1.Port < hp2.Port
   135  		}
   136  		return hp1.Address.Value < hp2.Address.Value
   137  	}
   138  	return order1 < order2
   139  }
   140  
   141  // SortHostPorts sorts the given HostPort slice according to the
   142  // sortOrder of each HostPort's embedded Address and the preferIpv6
   143  // flag. See Address.sortOrder() for more info.
   144  func SortHostPorts(hps []HostPort, preferIPv6 bool) {
   145  	if preferIPv6 {
   146  		sort.Sort(hostPortsPreferringIPv6Slice{hostPortsPreferringIPv4Slice(hps)})
   147  	} else {
   148  		sort.Sort(hostPortsPreferringIPv4Slice(hps))
   149  	}
   150  }
   151  
   152  var netLookupIP = net.LookupIP
   153  
   154  // ResolveOrDropHostnames tries to resolve each address of type
   155  // HostName (except for "localhost" - it's kept unchanged) using the
   156  // local resolver. If successful, each IP address corresponding to the
   157  // hostname is inserted in the same order. If not successful, a debug
   158  // log is added and the hostname is removed from the list. Duplicated
   159  // addresses after the resolving is done are removed.
   160  func ResolveOrDropHostnames(hps []HostPort) []HostPort {
   161  	uniqueAddrs := set.NewStrings()
   162  	result := make([]HostPort, 0, len(hps))
   163  	for _, hp := range hps {
   164  		val := hp.Value
   165  		if uniqueAddrs.Contains(val) {
   166  			continue
   167  		}
   168  		// localhost is special - do not resolve it, because it can be
   169  		// used both as an IPv4 or IPv6 endpoint (e.g. in IPv6-only
   170  		// networks).
   171  		if hp.Type != HostName || hp.Value == "localhost" {
   172  			result = append(result, hp)
   173  			uniqueAddrs.Add(val)
   174  			continue
   175  		}
   176  		ips, err := netLookupIP(val)
   177  		if err != nil {
   178  			logger.Debugf("removing unresolvable address %q: %v", val, err)
   179  			continue
   180  		}
   181  		for _, ip := range ips {
   182  			if ip == nil {
   183  				continue
   184  			}
   185  			addr := NewAddress(ip.String())
   186  			if !uniqueAddrs.Contains(addr.Value) {
   187  				result = append(result, HostPort{Address: addr, Port: hp.Port})
   188  				uniqueAddrs.Add(addr.Value)
   189  			}
   190  		}
   191  	}
   192  	return result
   193  }
   194  
   195  // FilterUnusableHostPorts returns a copy of the given HostPorts after
   196  // removing any addresses unlikely to be usable (ScopeMachineLocal or
   197  // ScopeLinkLocal).
   198  func FilterUnusableHostPorts(hps []HostPort) []HostPort {
   199  	filtered := make([]HostPort, 0, len(hps))
   200  	for _, hp := range hps {
   201  		switch hp.Scope {
   202  		case ScopeMachineLocal, ScopeLinkLocal:
   203  			continue
   204  		}
   205  		filtered = append(filtered, hp)
   206  	}
   207  	return filtered
   208  }
   209  
   210  // DropDuplicatedHostPorts removes any HostPorts duplicates from the
   211  // given slice and returns the result.
   212  func DropDuplicatedHostPorts(hps []HostPort) []HostPort {
   213  	uniqueHPs := set.NewStrings()
   214  	var result []HostPort
   215  	for _, hp := range hps {
   216  		if !uniqueHPs.Contains(hp.NetAddr()) {
   217  			uniqueHPs.Add(hp.NetAddr())
   218  			result = append(result, hp)
   219  		}
   220  	}
   221  	return result
   222  }
   223  
   224  // HostPortsToStrings converts each HostPort to string calling its
   225  // NetAddr() method.
   226  func HostPortsToStrings(hps []HostPort) []string {
   227  	result := make([]string, len(hps))
   228  	for i, hp := range hps {
   229  		result[i] = hp.NetAddr()
   230  	}
   231  	return result
   232  }
   233  
   234  // CollapseHostPorts returns a flattened list of HostPorts keeping the
   235  // same order they appear in serversHostPorts.
   236  func CollapseHostPorts(serversHostPorts [][]HostPort) []HostPort {
   237  	var collapsed []HostPort
   238  	for _, hps := range serversHostPorts {
   239  		collapsed = append(collapsed, hps...)
   240  	}
   241  	return collapsed
   242  }
   243  
   244  // EnsureFirstHostPort scans the given list of HostPorts and if
   245  // "first" is found, it moved to index 0. Otherwise, if "first" is not
   246  // in the list, it's inserted at index 0.
   247  func EnsureFirstHostPort(first HostPort, hps []HostPort) []HostPort {
   248  	var result []HostPort
   249  	found := false
   250  	for _, hp := range hps {
   251  		if hp.NetAddr() == first.NetAddr() && !found {
   252  			// Found, so skip it.
   253  			found = true
   254  			continue
   255  		}
   256  		result = append(result, hp)
   257  	}
   258  	// Insert it at the top.
   259  	result = append([]HostPort{first}, result...)
   260  	return result
   261  }