dial.go 6.84 KB
Newer Older
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
1
2
3
4
package conn

import (
	"fmt"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
5
6
	"math/rand"
	"net"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
7
	"strings"
8
	"syscall"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
9
10
11
12

	context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context"
	ma "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
	manet "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
13
	reuseport "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-reuseport"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
14

15
	addrutil "github.com/jbenet/go-ipfs/p2p/net/swarm/addr"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
16
17
18
19
20
21
22
23
24
25
26
27
28
29
	peer "github.com/jbenet/go-ipfs/p2p/peer"
	debugerror "github.com/jbenet/go-ipfs/util/debugerror"
)

// String returns the string rep of d.
func (d *Dialer) String() string {
	return fmt.Sprintf("<Dialer %s %s ...>", d.LocalPeer, d.LocalAddrs[0])
}

// Dial connects to a peer over a particular address
// Ensures raddr is part of peer.Addresses()
// Example: d.DialAddr(ctx, peer.Addresses()[0], peer)
func (d *Dialer) Dial(ctx context.Context, raddr ma.Multiaddr, remote peer.ID) (Conn, error) {

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
30
	maconn, err := d.rawConnDial(ctx, raddr, remote)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
	if err != nil {
		return nil, err
	}

	var connOut Conn
	var errOut error
	done := make(chan struct{})

	// do it async to ensure we respect don contexteone
	go func() {
		defer func() { done <- struct{}{} }()

		c, err := newSingleConn(ctx, d.LocalPeer, remote, maconn)
		if err != nil {
			errOut = err
			return
		}

		if d.PrivateKey == nil {
			log.Warning("dialer %s dialing INSECURELY %s at %s!", d, remote, raddr)
			connOut = c
			return
		}
		c2, err := newSecureConn(ctx, d.PrivateKey, c)
		if err != nil {
			errOut = err
			c.Close()
			return
		}

		connOut = c2
	}()

	select {
	case <-ctx.Done():
		maconn.Close()
		return nil, ctx.Err()
	case <-done:
		// whew, finished.
	}

	return connOut, errOut
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
75
76
77
78
79
// rawConnDial dials the underlying net.Conn + manet.Conns
func (d *Dialer) rawConnDial(ctx context.Context, raddr ma.Multiaddr, remote peer.ID) (manet.Conn, error) {

	// before doing anything, check we're going to be able to dial.
	// we may not support the given address.
80
	if _, _, err := manet.DialArgs(raddr); err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
81
82
83
84
85
86
87
88
89
90
		return nil, err
	}

	if strings.HasPrefix(raddr.String(), "/ip4/0.0.0.0") {
		return nil, debugerror.Errorf("Attempted to connect to zero address: %s", raddr)
	}

	// get local addr to use.
	laddr := pickLocalAddr(d.LocalAddrs, raddr)
	log.Debugf("%s dialing %s -- %s --> %s", d.LocalPeer, remote, laddr, raddr)
91

92
93
94
	// make a copy of the manet.Dialer, we may need to change its timeout.
	madialer := d.Dialer

95
	if laddr != nil && reuseport.Available() {
96
97
98
99
		// we're perhaps going to dial twice. half the timeout, so we can afford to.
		// otherwise our context would expire right after the first dial.
		madialer.Dialer.Timeout = (madialer.Dialer.Timeout / 2)

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
100
101
		// dial using reuseport.Dialer, because we're probably reusing addrs.
		// this is optimistic, as the reuseDial may fail to bind the port.
102
		if nconn, retry, reuseErr := reuseDial(madialer.Dialer, laddr, raddr); reuseErr == nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
103
			// if it worked, wrap the raw net.Conn with our manet.Conn
104
			log.Debugf("%s reuse worked! %s %s %s", d.LocalPeer, laddr, nconn.RemoteAddr(), nconn)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
105
			return manet.WrapNetConn(nconn)
106
107
108
		} else if !retry {
			// reuseDial is sure this is a legitimate dial failure, not a reuseport failure.
			return nil, reuseErr
109
		} else {
110
111
			// this is a failure to reuse port. log it.
			log.Debugf("%s port reuse failed: %s --> %s -- %s", d.LocalPeer, laddr, raddr, reuseErr)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
112
113
114
		}
	}

115
	return madialer.Dial(raddr)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
116
117
}

118
func reuseDial(dialer net.Dialer, laddr, raddr ma.Multiaddr) (conn net.Conn, retry bool, err error) {
119
120
121
122
123
	if laddr == nil {
		// if we're given no local address no sense in using reuseport to dial, dial out as usual.
		return nil, true, reuseport.ErrReuseFailed
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
124
125
	// give reuse.Dialer the manet.Dialer's Dialer.
	// (wow, Dialer should've so been an interface...)
126
	rd := reuseport.Dialer{dialer}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
127
128
129
130

	// get the local net.Addr manually
	rd.D.LocalAddr, err = manet.ToNetAddr(laddr)
	if err != nil {
131
		return nil, true, err // something wrong with laddr. retry without.
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
132
133
134
135
136
	}

	// get the raddr dial args for rd.dial
	network, netraddr, err := manet.DialArgs(raddr)
	if err != nil {
137
		return nil, true, err // something wrong with laddr. retry without.
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
138
139
140
	}

	// rd.Dial gets us a net.Conn with SO_REUSEPORT and SO_REUSEADDR set.
141
142
143
144
145
146
147
148
149
150
151
152
	conn, err = rd.Dial(network, netraddr)
	return conn, reuseErrShouldRetry(err), err // hey! it worked!
}

// reuseErrShouldRetry diagnoses whether to retry after a reuse error.
// if we failed to bind, we should retry. if bind worked and this is a
// real dial error (remote end didnt answer) then we should not retry.
func reuseErrShouldRetry(err error) bool {
	if err == nil {
		return false // hey, it worked! no need to retry.
	}

153
154
	// if it's a network timeout error, it's a legitimate failure.
	if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
155
		return false
156
157
	}

158
159
160
161
162
163
164
165
166
167
168
169
170
	errno, ok := err.(syscall.Errno)
	if !ok { // not an errno? who knows what this is. retry.
		return true
	}

	switch errno {
	case syscall.EADDRINUSE, syscall.EADDRNOTAVAIL:
		return true // failure to bind. retry.
	case syscall.ECONNREFUSED:
		return false // real dial error
	default:
		return true // optimistically default to retry.
	}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
171
172
173
174
175
176
177
}

func pickLocalAddr(laddrs []ma.Multiaddr, raddr ma.Multiaddr) (laddr ma.Multiaddr) {
	if len(laddrs) < 1 {
		return nil
	}

178
	// make sure that we ONLY use local addrs that match the remote addr.
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
179
180
181
182
183
	laddrs = manet.AddrMatch(raddr, laddrs)
	if len(laddrs) < 1 {
		return nil
	}

184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
	// make sure that we ONLY use local addrs that CAN dial the remote addr.
	// filter out all the local addrs that aren't capable
	raddrIPLayer := ma.Split(raddr)[0]
	raddrIsLoopback := manet.IsIPLoopback(raddrIPLayer)
	raddrIsLinkLocal := manet.IsIP6LinkLocal(raddrIPLayer)
	laddrs = addrutil.FilterAddrs(laddrs, func(a ma.Multiaddr) bool {
		laddrIPLayer := ma.Split(a)[0]
		laddrIsLoopback := manet.IsIPLoopback(laddrIPLayer)
		laddrIsLinkLocal := manet.IsIP6LinkLocal(laddrIPLayer)
		if laddrIsLoopback { // our loopback addrs can only dial loopbacks.
			return raddrIsLoopback
		}
		if laddrIsLinkLocal {
			return raddrIsLinkLocal // out linklocal addrs can only dial link locals.
		}
		return true
	})

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
202
203
204
205
206
207
	// TODO pick with a good heuristic
	// we use a random one for now to prevent bad addresses from making nodes unreachable
	// with a random selection, multiple tries may work.
	return laddrs[rand.Intn(len(laddrs))]
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
208
209
210
211
212
213
214
215
216
217
// MultiaddrProtocolsMatch returns whether two multiaddrs match in protocol stacks.
func MultiaddrProtocolsMatch(a, b ma.Multiaddr) bool {
	ap := a.Protocols()
	bp := b.Protocols()

	if len(ap) != len(bp) {
		return false
	}

	for i, api := range ap {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
218
		if api.Code != bp[i].Code {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
			return false
		}
	}

	return true
}

// MultiaddrNetMatch returns the first Multiaddr found to match  network.
func MultiaddrNetMatch(tgt ma.Multiaddr, srcs []ma.Multiaddr) ma.Multiaddr {
	for _, a := range srcs {
		if MultiaddrProtocolsMatch(tgt, a) {
			return a
		}
	}
	return nil
}