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 }