github.com/mh-cbon/go@v0.0.0-20160603070303-9e112a3fe4c0/src/net/ipsock.go (about) 1 // Copyright 2009 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Internet protocol family sockets 6 7 package net 8 9 import ( 10 "context" 11 ) 12 13 var ( 14 // supportsIPv4 reports whether the platform supports IPv4 15 // networking functionality. 16 supportsIPv4 bool 17 18 // supportsIPv6 reports whether the platform supports IPv6 19 // networking functionality. 20 supportsIPv6 bool 21 22 // supportsIPv4map reports whether the platform supports 23 // mapping an IPv4 address inside an IPv6 address at transport 24 // layer protocols. See RFC 4291, RFC 4038 and RFC 3493. 25 supportsIPv4map bool 26 ) 27 28 // An addrList represents a list of network endpoint addresses. 29 type addrList []Addr 30 31 // isIPv4 returns true if the Addr contains an IPv4 address. 32 func isIPv4(addr Addr) bool { 33 switch addr := addr.(type) { 34 case *TCPAddr: 35 return addr.IP.To4() != nil 36 case *UDPAddr: 37 return addr.IP.To4() != nil 38 case *IPAddr: 39 return addr.IP.To4() != nil 40 } 41 return false 42 } 43 44 // first returns the first address which satisfies strategy, or if 45 // none do, then the first address of any kind. 46 func (addrs addrList) first(strategy func(Addr) bool) Addr { 47 for _, addr := range addrs { 48 if strategy(addr) { 49 return addr 50 } 51 } 52 return addrs[0] 53 } 54 55 // partition divides an address list into two categories, using a 56 // strategy function to assign a boolean label to each address. 57 // The first address, and any with a matching label, are returned as 58 // primaries, while addresses with the opposite label are returned 59 // as fallbacks. For non-empty inputs, primaries is guaranteed to be 60 // non-empty. 61 func (addrs addrList) partition(strategy func(Addr) bool) (primaries, fallbacks addrList) { 62 var primaryLabel bool 63 for i, addr := range addrs { 64 label := strategy(addr) 65 if i == 0 || label == primaryLabel { 66 primaryLabel = label 67 primaries = append(primaries, addr) 68 } else { 69 fallbacks = append(fallbacks, addr) 70 } 71 } 72 return 73 } 74 75 // filterAddrList applies a filter to a list of IP addresses, 76 // yielding a list of Addr objects. Known filters are nil, ipv4only, 77 // and ipv6only. It returns every address when the filter is nil. 78 // The result contains at least one address when error is nil. 79 func filterAddrList(filter func(IPAddr) bool, ips []IPAddr, inetaddr func(IPAddr) Addr) (addrList, error) { 80 var addrs addrList 81 for _, ip := range ips { 82 if filter == nil || filter(ip) { 83 addrs = append(addrs, inetaddr(ip)) 84 } 85 } 86 if len(addrs) == 0 { 87 return nil, errNoSuitableAddress 88 } 89 return addrs, nil 90 } 91 92 // ipv4only reports whether the kernel supports IPv4 addressing mode 93 // and addr is an IPv4 address. 94 func ipv4only(addr IPAddr) bool { 95 return supportsIPv4 && addr.IP.To4() != nil 96 } 97 98 // ipv6only reports whether the kernel supports IPv6 addressing mode 99 // and addr is an IPv6 address except IPv4-mapped IPv6 address. 100 func ipv6only(addr IPAddr) bool { 101 return supportsIPv6 && len(addr.IP) == IPv6len && addr.IP.To4() == nil 102 } 103 104 // SplitHostPort splits a network address of the form "host:port", 105 // "[host]:port" or "[ipv6-host%zone]:port" into host or 106 // ipv6-host%zone and port. A literal address or host name for IPv6 107 // must be enclosed in square brackets, as in "[::1]:80", 108 // "[ipv6-host]:http" or "[ipv6-host%zone]:80". 109 func SplitHostPort(hostport string) (host, port string, err error) { 110 const ( 111 missingPort = "missing port in address" 112 tooManyColons = "too many colons in address" 113 ) 114 addrErr := func(addr, why string) (host, port string, err error) { 115 return "", "", &AddrError{Err: why, Addr: addr} 116 } 117 j, k := 0, 0 118 119 // The port starts after the last colon. 120 i := last(hostport, ':') 121 if i < 0 { 122 return addrErr(hostport, missingPort) 123 } 124 125 if hostport[0] == '[' { 126 // Expect the first ']' just before the last ':'. 127 end := byteIndex(hostport, ']') 128 if end < 0 { 129 return addrErr(hostport, "missing ']' in address") 130 } 131 switch end + 1 { 132 case len(hostport): 133 // There can't be a ':' behind the ']' now. 134 return addrErr(hostport, missingPort) 135 case i: 136 // The expected result. 137 default: 138 // Either ']' isn't followed by a colon, or it is 139 // followed by a colon that is not the last one. 140 if hostport[end+1] == ':' { 141 return addrErr(hostport, tooManyColons) 142 } 143 return addrErr(hostport, missingPort) 144 } 145 host = hostport[1:end] 146 j, k = 1, end+1 // there can't be a '[' resp. ']' before these positions 147 } else { 148 host = hostport[:i] 149 if byteIndex(host, ':') >= 0 { 150 return addrErr(hostport, tooManyColons) 151 } 152 if byteIndex(host, '%') >= 0 { 153 return addrErr(hostport, "missing brackets in address") 154 } 155 } 156 if byteIndex(hostport[j:], '[') >= 0 { 157 return addrErr(hostport, "unexpected '[' in address") 158 } 159 if byteIndex(hostport[k:], ']') >= 0 { 160 return addrErr(hostport, "unexpected ']' in address") 161 } 162 163 port = hostport[i+1:] 164 return host, port, nil 165 } 166 167 func splitHostZone(s string) (host, zone string) { 168 // The IPv6 scoped addressing zone identifier starts after the 169 // last percent sign. 170 if i := last(s, '%'); i > 0 { 171 host, zone = s[:i], s[i+1:] 172 } else { 173 host = s 174 } 175 return 176 } 177 178 // JoinHostPort combines host and port into a network address of the 179 // form "host:port" or, if host contains a colon or a percent sign, 180 // "[host]:port". 181 func JoinHostPort(host, port string) string { 182 // If host has colons or a percent sign, have to bracket it. 183 if byteIndex(host, ':') >= 0 || byteIndex(host, '%') >= 0 { 184 return "[" + host + "]:" + port 185 } 186 return host + ":" + port 187 } 188 189 // internetAddrList resolves addr, which may be a literal IP 190 // address or a DNS name, and returns a list of internet protocol 191 // family addresses. The result contains at least one address when 192 // error is nil. 193 func internetAddrList(ctx context.Context, net, addr string) (addrList, error) { 194 var ( 195 err error 196 host, port string 197 portnum int 198 ) 199 switch net { 200 case "tcp", "tcp4", "tcp6", "udp", "udp4", "udp6": 201 if addr != "" { 202 if host, port, err = SplitHostPort(addr); err != nil { 203 return nil, err 204 } 205 if portnum, err = LookupPort(net, port); err != nil { 206 return nil, err 207 } 208 } 209 case "ip", "ip4", "ip6": 210 if addr != "" { 211 host = addr 212 } 213 default: 214 return nil, UnknownNetworkError(net) 215 } 216 inetaddr := func(ip IPAddr) Addr { 217 switch net { 218 case "tcp", "tcp4", "tcp6": 219 return &TCPAddr{IP: ip.IP, Port: portnum, Zone: ip.Zone} 220 case "udp", "udp4", "udp6": 221 return &UDPAddr{IP: ip.IP, Port: portnum, Zone: ip.Zone} 222 case "ip", "ip4", "ip6": 223 return &IPAddr{IP: ip.IP, Zone: ip.Zone} 224 default: 225 panic("unexpected network: " + net) 226 } 227 } 228 if host == "" { 229 return addrList{inetaddr(IPAddr{})}, nil 230 } 231 // Try as a literal IP address. 232 var ip IP 233 if ip = parseIPv4(host); ip != nil { 234 return addrList{inetaddr(IPAddr{IP: ip})}, nil 235 } 236 var zone string 237 if ip, zone = parseIPv6(host, true); ip != nil { 238 return addrList{inetaddr(IPAddr{IP: ip, Zone: zone})}, nil 239 } 240 // Try as a DNS name. 241 ips, err := lookupIPContext(ctx, host) 242 if err != nil { 243 return nil, err 244 } 245 var filter func(IPAddr) bool 246 if net != "" && net[len(net)-1] == '4' { 247 filter = ipv4only 248 } 249 if net != "" && net[len(net)-1] == '6' { 250 filter = ipv6only 251 } 252 return filterAddrList(filter, ips, inetaddr) 253 }