diff options
Diffstat (limited to 'src/net/ipsock.go')
-rw-r--r-- | src/net/ipsock.go | 315 |
1 files changed, 315 insertions, 0 deletions
diff --git a/src/net/ipsock.go b/src/net/ipsock.go new file mode 100644 index 0000000..0f5da25 --- /dev/null +++ b/src/net/ipsock.go @@ -0,0 +1,315 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package net + +import ( + "context" + "internal/bytealg" + "runtime" + "sync" +) + +// BUG(rsc,mikio): On DragonFly BSD and OpenBSD, listening on the +// "tcp" and "udp" networks does not listen for both IPv4 and IPv6 +// connections. This is due to the fact that IPv4 traffic will not be +// routed to an IPv6 socket - two separate sockets are required if +// both address families are to be supported. +// See inet6(4) for details. + +type ipStackCapabilities struct { + sync.Once // guards following + ipv4Enabled bool + ipv6Enabled bool + ipv4MappedIPv6Enabled bool +} + +var ipStackCaps ipStackCapabilities + +// supportsIPv4 reports whether the platform supports IPv4 networking +// functionality. +func supportsIPv4() bool { + ipStackCaps.Once.Do(ipStackCaps.probe) + return ipStackCaps.ipv4Enabled +} + +// supportsIPv6 reports whether the platform supports IPv6 networking +// functionality. +func supportsIPv6() bool { + ipStackCaps.Once.Do(ipStackCaps.probe) + return ipStackCaps.ipv6Enabled +} + +// supportsIPv4map reports whether the platform supports mapping an +// IPv4 address inside an IPv6 address at transport layer +// protocols. See RFC 4291, RFC 4038 and RFC 3493. +func supportsIPv4map() bool { + // Some operating systems provide no support for mapping IPv4 + // addresses to IPv6, and a runtime check is unnecessary. + switch runtime.GOOS { + case "dragonfly", "openbsd": + return false + } + + ipStackCaps.Once.Do(ipStackCaps.probe) + return ipStackCaps.ipv4MappedIPv6Enabled +} + +// An addrList represents a list of network endpoint addresses. +type addrList []Addr + +// isIPv4 reports whether addr contains an IPv4 address. +func isIPv4(addr Addr) bool { + switch addr := addr.(type) { + case *TCPAddr: + return addr.IP.To4() != nil + case *UDPAddr: + return addr.IP.To4() != nil + case *IPAddr: + return addr.IP.To4() != nil + } + return false +} + +// isNotIPv4 reports whether addr does not contain an IPv4 address. +func isNotIPv4(addr Addr) bool { return !isIPv4(addr) } + +// forResolve returns the most appropriate address in address for +// a call to ResolveTCPAddr, ResolveUDPAddr, or ResolveIPAddr. +// IPv4 is preferred, unless addr contains an IPv6 literal. +func (addrs addrList) forResolve(network, addr string) Addr { + var want6 bool + switch network { + case "ip": + // IPv6 literal (addr does NOT contain a port) + want6 = count(addr, ':') > 0 + case "tcp", "udp": + // IPv6 literal. (addr contains a port, so look for '[') + want6 = count(addr, '[') > 0 + } + if want6 { + return addrs.first(isNotIPv4) + } + return addrs.first(isIPv4) +} + +// first returns the first address which satisfies strategy, or if +// none do, then the first address of any kind. +func (addrs addrList) first(strategy func(Addr) bool) Addr { + for _, addr := range addrs { + if strategy(addr) { + return addr + } + } + return addrs[0] +} + +// partition divides an address list into two categories, using a +// strategy function to assign a boolean label to each address. +// The first address, and any with a matching label, are returned as +// primaries, while addresses with the opposite label are returned +// as fallbacks. For non-empty inputs, primaries is guaranteed to be +// non-empty. +func (addrs addrList) partition(strategy func(Addr) bool) (primaries, fallbacks addrList) { + var primaryLabel bool + for i, addr := range addrs { + label := strategy(addr) + if i == 0 || label == primaryLabel { + primaryLabel = label + primaries = append(primaries, addr) + } else { + fallbacks = append(fallbacks, addr) + } + } + return +} + +// filterAddrList applies a filter to a list of IP addresses, +// yielding a list of Addr objects. Known filters are nil, ipv4only, +// and ipv6only. It returns every address when the filter is nil. +// The result contains at least one address when error is nil. +func filterAddrList(filter func(IPAddr) bool, ips []IPAddr, inetaddr func(IPAddr) Addr, originalAddr string) (addrList, error) { + var addrs addrList + for _, ip := range ips { + if filter == nil || filter(ip) { + addrs = append(addrs, inetaddr(ip)) + } + } + if len(addrs) == 0 { + return nil, &AddrError{Err: errNoSuitableAddress.Error(), Addr: originalAddr} + } + return addrs, nil +} + +// ipv4only reports whether addr is an IPv4 address. +func ipv4only(addr IPAddr) bool { + return addr.IP.To4() != nil +} + +// ipv6only reports whether addr is an IPv6 address except IPv4-mapped IPv6 address. +func ipv6only(addr IPAddr) bool { + return len(addr.IP) == IPv6len && addr.IP.To4() == nil +} + +// SplitHostPort splits a network address of the form "host:port", +// "host%zone:port", "[host]:port" or "[host%zone]:port" into host or +// host%zone and port. +// +// A literal IPv6 address in hostport must be enclosed in square +// brackets, as in "[::1]:80", "[::1%lo0]:80". +// +// See func Dial for a description of the hostport parameter, and host +// and port results. +func SplitHostPort(hostport string) (host, port string, err error) { + const ( + missingPort = "missing port in address" + tooManyColons = "too many colons in address" + ) + addrErr := func(addr, why string) (host, port string, err error) { + return "", "", &AddrError{Err: why, Addr: addr} + } + j, k := 0, 0 + + // The port starts after the last colon. + i := last(hostport, ':') + if i < 0 { + return addrErr(hostport, missingPort) + } + + if hostport[0] == '[' { + // Expect the first ']' just before the last ':'. + end := bytealg.IndexByteString(hostport, ']') + if end < 0 { + return addrErr(hostport, "missing ']' in address") + } + switch end + 1 { + case len(hostport): + // There can't be a ':' behind the ']' now. + return addrErr(hostport, missingPort) + case i: + // The expected result. + default: + // Either ']' isn't followed by a colon, or it is + // followed by a colon that is not the last one. + if hostport[end+1] == ':' { + return addrErr(hostport, tooManyColons) + } + return addrErr(hostport, missingPort) + } + host = hostport[1:end] + j, k = 1, end+1 // there can't be a '[' resp. ']' before these positions + } else { + host = hostport[:i] + if bytealg.IndexByteString(host, ':') >= 0 { + return addrErr(hostport, tooManyColons) + } + } + if bytealg.IndexByteString(hostport[j:], '[') >= 0 { + return addrErr(hostport, "unexpected '[' in address") + } + if bytealg.IndexByteString(hostport[k:], ']') >= 0 { + return addrErr(hostport, "unexpected ']' in address") + } + + port = hostport[i+1:] + return host, port, nil +} + +func splitHostZone(s string) (host, zone string) { + // The IPv6 scoped addressing zone identifier starts after the + // last percent sign. + if i := last(s, '%'); i > 0 { + host, zone = s[:i], s[i+1:] + } else { + host = s + } + return +} + +// JoinHostPort combines host and port into a network address of the +// form "host:port". If host contains a colon, as found in literal +// IPv6 addresses, then JoinHostPort returns "[host]:port". +// +// See func Dial for a description of the host and port parameters. +func JoinHostPort(host, port string) string { + // We assume that host is a literal IPv6 address if host has + // colons. + if bytealg.IndexByteString(host, ':') >= 0 { + return "[" + host + "]:" + port + } + return host + ":" + port +} + +// internetAddrList resolves addr, which may be a literal IP +// address or a DNS name, and returns a list of internet protocol +// family addresses. The result contains at least one address when +// error is nil. +func (r *Resolver) internetAddrList(ctx context.Context, net, addr string) (addrList, error) { + var ( + err error + host, port string + portnum int + ) + switch net { + case "tcp", "tcp4", "tcp6", "udp", "udp4", "udp6": + if addr != "" { + if host, port, err = SplitHostPort(addr); err != nil { + return nil, err + } + if portnum, err = r.LookupPort(ctx, net, port); err != nil { + return nil, err + } + } + case "ip", "ip4", "ip6": + if addr != "" { + host = addr + } + default: + return nil, UnknownNetworkError(net) + } + inetaddr := func(ip IPAddr) Addr { + switch net { + case "tcp", "tcp4", "tcp6": + return &TCPAddr{IP: ip.IP, Port: portnum, Zone: ip.Zone} + case "udp", "udp4", "udp6": + return &UDPAddr{IP: ip.IP, Port: portnum, Zone: ip.Zone} + case "ip", "ip4", "ip6": + return &IPAddr{IP: ip.IP, Zone: ip.Zone} + default: + panic("unexpected network: " + net) + } + } + if host == "" { + return addrList{inetaddr(IPAddr{})}, nil + } + + // Try as a literal IP address, then as a DNS name. + ips, err := r.lookupIPAddr(ctx, net, host) + if err != nil { + return nil, err + } + // Issue 18806: if the machine has halfway configured + // IPv6 such that it can bind on "::" (IPv6unspecified) + // but not connect back to that same address, fall + // back to dialing 0.0.0.0. + if len(ips) == 1 && ips[0].IP.Equal(IPv6unspecified) { + ips = append(ips, IPAddr{IP: IPv4zero}) + } + + var filter func(IPAddr) bool + if net != "" && net[len(net)-1] == '4' { + filter = ipv4only + } + if net != "" && net[len(net)-1] == '6' { + filter = ipv6only + } + return filterAddrList(filter, ips, inetaddr, host) +} + +func loopbackIP(net string) IP { + if net != "" && net[len(net)-1] == '6' { + return IPv6loopback + } + return IP{127, 0, 0, 1} +} |