// 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. // Internet protocol family sockets package net import ( "errors" "time" ) var ( // supportsIPv4 reports whether the platform supports IPv4 // networking functionality. supportsIPv4 bool // supportsIPv6 reports whether the platform supports IPv6 // networking functionality. supportsIPv6 bool // 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. supportsIPv4map bool ) func init() { sysInit() supportsIPv4 = probeIPv4Stack() supportsIPv6, supportsIPv4map = probeIPv6Stack() } // A netaddr represents a network endpoint address or a list of // network endpoint addresses. type netaddr interface { // toAddr returns the address represented in Addr interface. // It returns a nil interface when the address is nil. toAddr() Addr } // An addrList represents a list of network endpoint addresses. type addrList []netaddr func (al addrList) toAddr() Addr { switch len(al) { case 0: return nil case 1: return al[0].toAddr() default: // For now, we'll roughly pick first one without // considering dealing with any preferences such as // DNS TTL, transport path quality, network routing // information. return al[0].toAddr() } } var errNoSuitableAddress = errors.New("no suitable address found") // firstFavoriteAddr returns an address or a list of addresses that // implement the netaddr interface. Known filters are nil, ipv4only // and ipv6only. It returns any address when filter is nil. The result // contains at least one address when error is nil. func firstFavoriteAddr(filter func(IP) IP, ips []IP, inetaddr func(IP) netaddr) (netaddr, error) { if filter != nil { return firstSupportedAddr(filter, ips, inetaddr) } var ( ipv4, ipv6, swap bool list addrList ) for _, ip := range ips { // We'll take any IP address, but since the dialing // code does not yet try multiple addresses // effectively, prefer to use an IPv4 address if // possible. This is especially relevant if localhost // resolves to [ipv6-localhost, ipv4-localhost]. Too // much code assumes localhost == ipv4-localhost. if ip4 := ipv4only(ip); ip4 != nil && !ipv4 { list = append(list, inetaddr(ip4)) ipv4 = true if ipv6 { swap = true } } else if ip6 := ipv6only(ip); ip6 != nil && !ipv6 { list = append(list, inetaddr(ip6)) ipv6 = true } if ipv4 && ipv6 { if swap { list[0], list[1] = list[1], list[0] } break } } switch len(list) { case 0: return nil, errNoSuitableAddress case 1: return list[0], nil default: return list, nil } } func firstSupportedAddr(filter func(IP) IP, ips []IP, inetaddr func(IP) netaddr) (netaddr, error) { for _, ip := range ips { if ip := filter(ip); ip != nil { return inetaddr(ip), nil } } return nil, errNoSuitableAddress } // ipv4only returns IPv4 addresses that we can use with the kernel's // IPv4 addressing modes. If ip is an IPv4 address, ipv4only returns ip. // Otherwise it returns nil. func ipv4only(ip IP) IP { if supportsIPv4 && ip.To4() != nil { return ip } return nil } // ipv6only returns IPv6 addresses that we can use with the kernel's // IPv6 addressing modes. It returns IPv4-mapped IPv6 addresses as // nils and returns other IPv6 address types as IPv6 addresses. func ipv6only(ip IP) IP { if supportsIPv6 && len(ip) == IPv6len && ip.To4() == nil { return ip } return nil } // SplitHostPort splits a network address of the form "host:port", // "[host]:port" or "[ipv6-host%zone]:port" into host or // ipv6-host%zone and port. A literal address or host name for IPv6 // must be enclosed in square brackets, as in "[::1]:80", // "[ipv6-host]:http" or "[ipv6-host%zone]:80". func SplitHostPort(hostport string) (host, port string, err error) { j, k := 0, 0 // The port starts after the last colon. i := last(hostport, ':') if i < 0 { goto missingPort } if hostport[0] == '[' { // Expect the first ']' just before the last ':'. end := byteIndex(hostport, ']') if end < 0 { err = &AddrError{"missing ']' in address", hostport} return } switch end + 1 { case len(hostport): // There can't be a ':' behind the ']' now. goto 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] == ':' { goto tooManyColons } goto missingPort } host = hostport[1:end] j, k = 1, end+1 // there can't be a '[' resp. ']' before these positions } else { host = hostport[:i] if byteIndex(host, ':') >= 0 { goto tooManyColons } if byteIndex(host, '%') >= 0 { goto missingBrackets } } if byteIndex(hostport[j:], '[') >= 0 { err = &AddrError{"unexpected '[' in address", hostport} return } if byteIndex(hostport[k:], ']') >= 0 { err = &AddrError{"unexpected ']' in address", hostport} return } port = hostport[i+1:] return missingPort: err = &AddrError{"missing port in address", hostport} return tooManyColons: err = &AddrError{"too many colons in address", hostport} return missingBrackets: err = &AddrError{"missing brackets in address", hostport} return } 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" or, if host contains a colon or a percent sign, // "[host]:port". func JoinHostPort(host, port string) string { // If host has colons or a percent sign, have to bracket it. if byteIndex(host, ':') >= 0 || byteIndex(host, '%') >= 0 { return "[" + host + "]:" + port } return host + ":" + port } // resolveInternetAddr resolves addr that is either a literal IP // address or a DNS name and returns an internet protocol family // address. It returns a list that contains a pair of different // address family addresses when addr is a DNS name and the name has // multiple address family records. The result contains at least one // address when error is nil. func resolveInternetAddr(net, addr string, deadline time.Time) (netaddr, error) { var ( err error host, port, zone 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 = parsePort(net, port); err != nil { return nil, err } } case "ip", "ip4", "ip6": if addr != "" { host = addr } default: return nil, UnknownNetworkError(net) } inetaddr := func(ip IP) netaddr { switch net { case "tcp", "tcp4", "tcp6": return &TCPAddr{IP: ip, Port: portnum, Zone: zone} case "udp", "udp4", "udp6": return &UDPAddr{IP: ip, Port: portnum, Zone: zone} case "ip", "ip4", "ip6": return &IPAddr{IP: ip, Zone: zone} default: panic("unexpected network: " + net) } } if host == "" { return inetaddr(nil), nil } // Try as a literal IP address. var ip IP if ip = parseIPv4(host); ip != nil { return inetaddr(ip), nil } if ip, zone = parseIPv6(host, true); ip != nil { return inetaddr(ip), nil } // Try as a DNS name. host, zone = splitHostZone(host) ips, err := lookupIPDeadline(host, deadline) if err != nil { return nil, err } var filter func(IP) IP if net != "" && net[len(net)-1] == '4' { filter = ipv4only } if net != "" && net[len(net)-1] == '6' || zone != "" { filter = ipv6only } return firstFavoriteAddr(filter, ips, inetaddr) } func zoneToString(zone int) string { if zone == 0 { return "" } if ifi, err := InterfaceByIndex(zone); err == nil { return ifi.Name } return itod(uint(zone)) } func zoneToInt(zone string) int { if zone == "" { return 0 } if ifi, err := InterfaceByName(zone); err == nil { return ifi.Index } n, _, _ := dtoi(zone, 0) return n }