dial.go 6.72 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
	if laddr != nil && reuseport.Available() {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
93
94
		// dial using reuseport.Dialer, because we're probably reusing addrs.
		// this is optimistic, as the reuseDial may fail to bind the port.
95
		if nconn, retry, reuseErr := d.reuseDial(laddr, raddr); reuseErr == nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
96
			// if it worked, wrap the raw net.Conn with our manet.Conn
97
			log.Debugf("%s reuse worked! %s %s %s", d.LocalPeer, laddr, nconn.RemoteAddr(), nconn)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
98
			return manet.WrapNetConn(nconn)
99
100
101
		} else if !retry {
			// reuseDial is sure this is a legitimate dial failure, not a reuseport failure.
			return nil, reuseErr
102
		} else {
103
104
			// 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
105
106
107
		}
	}

108
	// no local addr, or reuseport failed. just dial straight with a new port.
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
109
110
111
	return d.Dialer.Dial(raddr)
}

112
113
114
115
116
117
func (d *Dialer) reuseDial(laddr, raddr ma.Multiaddr) (conn net.Conn, retry bool, err error) {
	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
	}

118
119
120
	// half the timeout so we can retry regularly if this fails.
	d.Dialer.Dialer.Timeout = (d.Dialer.Dialer.Timeout / 2)

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

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

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

	// rd.Dial gets us a net.Conn with SO_REUSEPORT and SO_REUSEADDR set.
138
139
140
141
142
143
144
145
146
147
148
149
	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.
	}

150
151
152
153
154
	// if it's a network timeout error, it's a legitimate failure.
	if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
		return true
	}

155
156
157
158
159
160
161
162
163
164
165
166
167
	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
168
169
170
171
172
173
174
}

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

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

181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
	// 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
199
200
201
202
203
204
	// 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
205
206
207
208
209
210
211
212
213
214
// 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
215
		if api.Code != bp[i].Code {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
			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
}