dial.go 6.47 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
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
118
119
120
121
122
123
124
	// 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 {
125
		return nil, true, err // something wrong with laddr. retry without.
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
126
127
128
129
130
	}

	// get the raddr dial args for rd.dial
	network, netraddr, err := manet.DialArgs(raddr)
	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
	}

	// rd.Dial gets us a net.Conn with SO_REUSEPORT and SO_REUSEADDR set.
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
	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.
	}

	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
160
161
162
163
164
165
166
}

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

167
	// make sure that we ONLY use local addrs that match the remote addr.
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
168
169
170
171
172
	laddrs = manet.AddrMatch(raddr, laddrs)
	if len(laddrs) < 1 {
		return nil
	}

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