Skip to content

Commit a5dec38

Browse files
Paul Markscixtor
Paul Marks
authored andcommitted
net: make multi-IP resolution more flexible.
Remove the "netaddr" type, which ambiguously represented either one address, or a list of addresses. Instead, use "addrList" wherever multiple addresses are supported. The "first" method returns the first address matching some condition (e.g. "is it IPv4?"), primarily to support legacy code that can't handle multiple addresses. The "partition" method splits an addrList into two categories, as defined by some strategy function. This is useful for implementing Happy Eyeballs, and similar two-channel algorithms. Finally, internetAddrList (formerly resolveInternetAddr) no longer mangles the ordering defined by getaddrinfo. In the future, this may be used by a sequential Dial implementation. Updates #8453, #8455. Change-Id: I7375f4c34481580ab40e31d33002a4073a0474f3 Reviewed-on: https://go-review.googlesource.com/8360 Reviewed-by: Mikio Hara <[email protected]> Run-TryBot: Mikio Hara <[email protected]> TryBot-Result: Gobot Gobot <[email protected]>
1 parent 4b21be4 commit a5dec38

13 files changed

+225
-168
lines changed

src/net/dial.go

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ func parseNetwork(net string) (afnet string, proto int, err error) {
9595
return "", 0, UnknownNetworkError(net)
9696
}
9797

98-
func resolveAddr(op, net, addr string, deadline time.Time) (netaddr, error) {
98+
func resolveAddrList(op, net, addr string, deadline time.Time) (addrList, error) {
9999
afnet, _, err := parseNetwork(net)
100100
if err != nil {
101101
return nil, err
@@ -105,9 +105,13 @@ func resolveAddr(op, net, addr string, deadline time.Time) (netaddr, error) {
105105
}
106106
switch afnet {
107107
case "unix", "unixgram", "unixpacket":
108-
return ResolveUnixAddr(afnet, addr)
108+
addr, err := ResolveUnixAddr(afnet, addr)
109+
if err != nil {
110+
return nil, err
111+
}
112+
return addrList{addr}, nil
109113
}
110-
return resolveInternetAddr(afnet, addr, deadline)
114+
return internetAddrList(afnet, addr, deadline)
111115
}
112116

113117
// Dial connects to the address on the named network.
@@ -155,21 +159,25 @@ func DialTimeout(network, address string, timeout time.Duration) (Conn, error) {
155159
// See func Dial for a description of the network and address
156160
// parameters.
157161
func (d *Dialer) Dial(network, address string) (Conn, error) {
158-
ra, err := resolveAddr("dial", network, address, d.deadline())
162+
addrs, err := resolveAddrList("dial", network, address, d.deadline())
159163
if err != nil {
160164
return nil, &OpError{Op: "dial", Net: network, Addr: nil, Err: err}
161165
}
162166
var dialer func(deadline time.Time) (Conn, error)
163-
if ras, ok := ra.(addrList); ok && d.DualStack && network == "tcp" {
164-
dialer = func(deadline time.Time) (Conn, error) {
165-
return dialMulti(network, address, d.LocalAddr, ras, deadline)
167+
if d.DualStack && network == "tcp" {
168+
primaries, fallbacks := addrs.partition(isIPv4)
169+
if len(fallbacks) > 0 {
170+
dialer = func(deadline time.Time) (Conn, error) {
171+
return dialMulti(network, address, d.LocalAddr, addrList{primaries[0], fallbacks[0]}, deadline)
172+
}
166173
}
167-
} else {
174+
}
175+
if dialer == nil {
168176
dialer = func(deadline time.Time) (Conn, error) {
169-
return dialSingle(network, address, d.LocalAddr, ra.toAddr(), deadline)
177+
return dialSingle(network, address, d.LocalAddr, addrs.first(isIPv4), deadline)
170178
}
171179
}
172-
c, err := dial(network, ra.toAddr(), dialer, d.deadline())
180+
c, err := dial(network, addrs.first(isIPv4), dialer, d.deadline())
173181
if d.KeepAlive > 0 && err == nil {
174182
if tc, ok := c.(*TCPConn); ok {
175183
tc.SetKeepAlive(true)
@@ -206,7 +214,7 @@ func dialMulti(net, addr string, la Addr, ras addrList, deadline time.Time) (Con
206214
// unnecessary resource starvation.
207215
c.Close()
208216
}
209-
}(ra.toAddr())
217+
}(ra)
210218
}
211219
defer close(sig)
212220
lastErr := errTimeout
@@ -256,12 +264,12 @@ func dialSingle(net, addr string, la, ra Addr, deadline time.Time) (c Conn, err
256264
// "tcp6", "unix" or "unixpacket".
257265
// See Dial for the syntax of laddr.
258266
func Listen(net, laddr string) (Listener, error) {
259-
la, err := resolveAddr("listen", net, laddr, noDeadline)
267+
addrs, err := resolveAddrList("listen", net, laddr, noDeadline)
260268
if err != nil {
261269
return nil, &OpError{Op: "listen", Net: net, Addr: nil, Err: err}
262270
}
263271
var l Listener
264-
switch la := la.toAddr().(type) {
272+
switch la := addrs.first(isIPv4).(type) {
265273
case *TCPAddr:
266274
l, err = ListenTCP(net, la)
267275
case *UnixAddr:
@@ -280,12 +288,12 @@ func Listen(net, laddr string) (Listener, error) {
280288
// "udp6", "ip", "ip4", "ip6" or "unixgram".
281289
// See Dial for the syntax of laddr.
282290
func ListenPacket(net, laddr string) (PacketConn, error) {
283-
la, err := resolveAddr("listen", net, laddr, noDeadline)
291+
addrs, err := resolveAddrList("listen", net, laddr, noDeadline)
284292
if err != nil {
285293
return nil, &OpError{Op: "listen", Net: net, Addr: nil, Err: err}
286294
}
287295
var l PacketConn
288-
switch la := la.toAddr().(type) {
296+
switch la := addrs.first(isIPv4).(type) {
289297
case *UDPAddr:
290298
l, err = ListenUDP(net, la)
291299
case *IPAddr:

src/net/interface_linux.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -238,8 +238,8 @@ func parseProcNetIGMP(path string, ifi *Interface) []Addr {
238238
b[i/2], _ = xtoi2(f[0][i:i+2], 0)
239239
}
240240
i := *(*uint32)(unsafe.Pointer(&b[:4][0]))
241-
ifma := IPAddr{IP: IPv4(byte(i>>24), byte(i>>16), byte(i>>8), byte(i))}
242-
ifmat = append(ifmat, ifma.toAddr())
241+
ifma := &IPAddr{IP: IPv4(byte(i>>24), byte(i>>16), byte(i>>8), byte(i))}
242+
ifmat = append(ifmat, ifma)
243243
}
244244
}
245245
}
@@ -263,8 +263,8 @@ func parseProcNetIGMP6(path string, ifi *Interface) []Addr {
263263
for i := 0; i+1 < len(f[2]); i += 2 {
264264
b[i/2], _ = xtoi2(f[2][i:i+2], 0)
265265
}
266-
ifma := IPAddr{IP: IP{b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15]}}
267-
ifmat = append(ifmat, ifma.toAddr())
266+
ifma := &IPAddr{IP: IP{b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15]}}
267+
ifmat = append(ifmat, ifma)
268268
}
269269
}
270270
return ifmat

src/net/interface_windows.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,11 +217,11 @@ func interfaceMulticastAddrTable(ifi *Interface) ([]Addr, error) {
217217
case *syscall.SockaddrInet4:
218218
ifa := &IPAddr{IP: make(IP, IPv4len)}
219219
copy(ifa.IP, sav.Addr[:])
220-
ifat = append(ifat, ifa.toAddr())
220+
ifat = append(ifat, ifa)
221221
case *syscall.SockaddrInet6:
222222
ifa := &IPAddr{IP: make(IP, IPv6len)}
223223
copy(ifa.IP, sav.Addr[:])
224-
ifat = append(ifat, ifa.toAddr())
224+
ifat = append(ifat, ifa)
225225
}
226226
}
227227
}

src/net/iprawsock.go

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,6 @@ func (a *IPAddr) isWildcard() bool {
3030
return a.IP.IsUnspecified()
3131
}
3232

33-
func (a *IPAddr) toAddr() Addr {
34-
if a == nil {
35-
return nil
36-
}
37-
return a
38-
}
39-
4033
// ResolveIPAddr parses addr as an IP address of the form "host" or
4134
// "ipv6-host%zone" and resolves the domain name on the network net,
4235
// which must be "ip", "ip4" or "ip6".
@@ -53,9 +46,9 @@ func ResolveIPAddr(net, addr string) (*IPAddr, error) {
5346
default:
5447
return nil, UnknownNetworkError(net)
5548
}
56-
a, err := resolveInternetAddr(afnet, addr, noDeadline)
49+
addrs, err := internetAddrList(afnet, addr, noDeadline)
5750
if err != nil {
5851
return nil, err
5952
}
60-
return a.toAddr().(*IPAddr), nil
53+
return addrs.first(isIPv4).(*IPAddr), nil
6154
}

src/net/iprawsock_posix.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,10 @@ func (c *IPConn) ReadFrom(b []byte) (int, Addr, error) {
104104
return 0, nil, syscall.EINVAL
105105
}
106106
n, addr, err := c.ReadFromIP(b)
107-
return n, addr.toAddr(), err
107+
if addr == nil {
108+
return n, nil, err
109+
}
110+
return n, addr, err
108111
}
109112

110113
// ReadMsgIP reads a packet from c, copying the payload into b and the

src/net/ipsock.go

Lines changed: 61 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -26,87 +26,70 @@ var (
2626
supportsIPv4map bool
2727
)
2828

29-
// A netaddr represents a network endpoint address or a list of
30-
// network endpoint addresses.
31-
type netaddr interface {
32-
// toAddr returns the address represented in Addr interface.
33-
// It returns a nil interface when the address is nil.
34-
toAddr() Addr
35-
}
36-
3729
// An addrList represents a list of network endpoint addresses.
38-
type addrList []netaddr
30+
type addrList []Addr
3931

40-
func (al addrList) toAddr() Addr {
41-
switch len(al) {
42-
case 0:
43-
return nil
44-
case 1:
45-
return al[0].toAddr()
46-
default:
47-
// For now, we'll roughly pick first one without
48-
// considering dealing with any preferences such as
49-
// DNS TTL, transport path quality, network routing
50-
// information.
51-
return al[0].toAddr()
32+
// isIPv4 returns true if the Addr contains an IPv4 address.
33+
func isIPv4(addr Addr) bool {
34+
switch addr := addr.(type) {
35+
case *TCPAddr:
36+
return addr.IP.To4() != nil
37+
case *UDPAddr:
38+
return addr.IP.To4() != nil
39+
case *IPAddr:
40+
return addr.IP.To4() != nil
5241
}
42+
return false
5343
}
5444

55-
var errNoSuitableAddress = errors.New("no suitable address found")
56-
57-
// firstFavoriteAddr returns an address or a list of addresses that
58-
// implement the netaddr interface. Known filters are nil, ipv4only
59-
// and ipv6only. It returns any address when filter is nil. The result
60-
// contains at least one address when error is nil.
61-
func firstFavoriteAddr(filter func(IPAddr) bool, ips []IPAddr, inetaddr func(IPAddr) netaddr) (netaddr, error) {
62-
if filter != nil {
63-
return firstSupportedAddr(filter, ips, inetaddr)
64-
}
65-
var (
66-
ipv4, ipv6, swap bool
67-
list addrList
68-
)
69-
for _, ip := range ips {
70-
// We'll take any IP address, but since the dialing
71-
// code does not yet try multiple addresses
72-
// effectively, prefer to use an IPv4 address if
73-
// possible. This is especially relevant if localhost
74-
// resolves to [ipv6-localhost, ipv4-localhost]. Too
75-
// much code assumes localhost == ipv4-localhost.
76-
if ipv4only(ip) && !ipv4 {
77-
list = append(list, inetaddr(ip))
78-
ipv4 = true
79-
if ipv6 {
80-
swap = true
81-
}
82-
} else if ipv6only(ip) && !ipv6 {
83-
list = append(list, inetaddr(ip))
84-
ipv6 = true
85-
}
86-
if ipv4 && ipv6 {
87-
if swap {
88-
list[0], list[1] = list[1], list[0]
89-
}
90-
break
45+
// first returns the first address which satisfies strategy, or if
46+
// none do, then the first address of any kind.
47+
func (addrs addrList) first(strategy func(Addr) bool) Addr {
48+
for _, addr := range addrs {
49+
if strategy(addr) {
50+
return addr
9151
}
9252
}
93-
switch len(list) {
94-
case 0:
95-
return nil, errNoSuitableAddress
96-
case 1:
97-
return list[0], nil
98-
default:
99-
return list, nil
53+
return addrs[0]
54+
}
55+
56+
// partition divides an address list into two categories, using a
57+
// strategy function to assign a boolean label to each address.
58+
// The first address, and any with a matching label, are returned as
59+
// primaries, while addresses with the opposite label are returned
60+
// as fallbacks. For non-empty inputs, primaries is guaranteed to be
61+
// non-empty.
62+
func (addrs addrList) partition(strategy func(Addr) bool) (primaries, fallbacks addrList) {
63+
var primaryLabel bool
64+
for i, addr := range addrs {
65+
label := strategy(addr)
66+
if i == 0 || label == primaryLabel {
67+
primaryLabel = label
68+
primaries = append(primaries, addr)
69+
} else {
70+
fallbacks = append(fallbacks, addr)
71+
}
10072
}
73+
return
10174
}
10275

103-
func firstSupportedAddr(filter func(IPAddr) bool, ips []IPAddr, inetaddr func(IPAddr) netaddr) (netaddr, error) {
76+
var errNoSuitableAddress = errors.New("no suitable address found")
77+
78+
// filterAddrList applies a filter to a list of IP addresses,
79+
// yielding a list of Addr objects. Known filters are nil, ipv4only,
80+
// and ipv6only. It returns every address when the filter is nil.
81+
// The result contains at least one address when error is nil.
82+
func filterAddrList(filter func(IPAddr) bool, ips []IPAddr, inetaddr func(IPAddr) Addr) (addrList, error) {
83+
var addrs addrList
10484
for _, ip := range ips {
105-
if filter(ip) {
106-
return inetaddr(ip), nil
85+
if filter == nil || filter(ip) {
86+
addrs = append(addrs, inetaddr(ip))
10787
}
10888
}
109-
return nil, errNoSuitableAddress
89+
if len(addrs) == 0 {
90+
return nil, errNoSuitableAddress
91+
}
92+
return addrs, nil
11093
}
11194

11295
// ipv4only reports whether the kernel supports IPv4 addressing mode
@@ -214,13 +197,11 @@ func JoinHostPort(host, port string) string {
214197
return host + ":" + port
215198
}
216199

217-
// resolveInternetAddr resolves addr that is either a literal IP
218-
// address or a DNS name and returns an internet protocol family
219-
// address. It returns a list that contains a pair of different
220-
// address family addresses when addr is a DNS name and the name has
221-
// multiple address family records. The result contains at least one
222-
// address when error is nil.
223-
func resolveInternetAddr(net, addr string, deadline time.Time) (netaddr, error) {
200+
// internetAddrList resolves addr, which may be a literal IP
201+
// address or a DNS name, and returns a list of internet protocol
202+
// family addresses. The result contains at least one address when
203+
// error is nil.
204+
func internetAddrList(net, addr string, deadline time.Time) (addrList, error) {
224205
var (
225206
err error
226207
host, port string
@@ -243,7 +224,7 @@ func resolveInternetAddr(net, addr string, deadline time.Time) (netaddr, error)
243224
default:
244225
return nil, UnknownNetworkError(net)
245226
}
246-
inetaddr := func(ip IPAddr) netaddr {
227+
inetaddr := func(ip IPAddr) Addr {
247228
switch net {
248229
case "tcp", "tcp4", "tcp6":
249230
return &TCPAddr{IP: ip.IP, Port: portnum, Zone: ip.Zone}
@@ -256,16 +237,16 @@ func resolveInternetAddr(net, addr string, deadline time.Time) (netaddr, error)
256237
}
257238
}
258239
if host == "" {
259-
return inetaddr(IPAddr{}), nil
240+
return addrList{inetaddr(IPAddr{})}, nil
260241
}
261242
// Try as a literal IP address.
262243
var ip IP
263244
if ip = parseIPv4(host); ip != nil {
264-
return inetaddr(IPAddr{IP: ip}), nil
245+
return addrList{inetaddr(IPAddr{IP: ip})}, nil
265246
}
266247
var zone string
267248
if ip, zone = parseIPv6(host, true); ip != nil {
268-
return inetaddr(IPAddr{IP: ip, Zone: zone}), nil
249+
return addrList{inetaddr(IPAddr{IP: ip, Zone: zone})}, nil
269250
}
270251
// Try as a DNS name.
271252
ips, err := lookupIPDeadline(host, deadline)
@@ -279,7 +260,7 @@ func resolveInternetAddr(net, addr string, deadline time.Time) (netaddr, error)
279260
if net != "" && net[len(net)-1] == '6' {
280261
filter = ipv6only
281262
}
282-
return firstFavoriteAddr(filter, ips, inetaddr)
263+
return filterAddrList(filter, ips, inetaddr)
283264
}
284265

285266
func zoneToString(zone int) string {

0 commit comments

Comments
 (0)