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

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

	context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context"
11
	ma "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
12
	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
	lgbl "github.com/jbenet/go-ipfs/util/eventlog/loggables"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
15

16
	addrutil "github.com/jbenet/go-ipfs/p2p/net/swarm/addr"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
17
	peer "github.com/jbenet/go-ipfs/p2p/peer"
18
	debugerror "github.com/jbenet/go-ipfs/util/debugerror"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
19 20
)

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

26
// Dial connects to a peer over a particular address
27 28
// Ensures raddr is part of peer.Addresses()
// Example: d.DialAddr(ctx, peer.Addresses()[0], peer)
29
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 31
	logdial := lgbl.Dial("conn", d.LocalPeer, remote, nil, raddr)
	defer log.EventBegin(ctx, "connDial", logdial).Done()
32

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
33
	maconn, err := d.rawConnDial(ctx, raddr, remote)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
34
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
35 36
		logdial["dial"] = "failure"
		logdial["error"] = err
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
37 38 39
		return nil, err
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
40 41 42 43 44 45 46 47 48 49 50 51 52
	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
		}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
53

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
54 55
		if d.PrivateKey == nil {
			log.Warning("dialer %s dialing INSECURELY %s at %s!", d, remote, raddr)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
56
			log.Event(ctx, "connDialInsecure", logdial)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
57 58 59
			connOut = c
			return
		}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
60 61

		defer log.EventBegin(ctx, "connDialEncrypt", logdial).Done()
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
62 63
		c2, err := newSecureConn(ctx, d.PrivateKey, c)
		if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
64
			logdial["error"] = err
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
65 66 67 68
			errOut = err
			c.Close()
			return
		}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
69

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
70 71
		connOut = c2
	}()
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
72

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
73 74
	select {
	case <-ctx.Done():
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
75
		maconn.Close()
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
76
		logdial["error"] = ctx.Err()
77
		return nil, ctx.Err()
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
78 79
	case <-done:
		// whew, finished.
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
80 81
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
82 83 84 85 86 87 88 89
	if errOut != nil {
		logdial["error"] = errOut
		logdial["dial"] = "failure"
		return nil, errOut
	}

	logdial["dial"] = "success"
	return connOut, nil
90 91
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
92 93 94 95 96
// 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.
97
	if _, _, err := manet.DialArgs(raddr); err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
98 99 100 101
		return nil, err
	}

	if strings.HasPrefix(raddr.String(), "/ip4/0.0.0.0") {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
102
		log.Event(ctx, "connDialZeroAddr", lgbl.Dial("conn", d.LocalPeer, remote, nil, raddr))
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
103 104 105 106 107
		return nil, debugerror.Errorf("Attempted to connect to zero address: %s", raddr)
	}

	// get local addr to use.
	laddr := pickLocalAddr(d.LocalAddrs, raddr)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
108 109
	logdial := lgbl.Dial("conn", d.LocalPeer, remote, laddr, raddr)
	defer log.EventBegin(ctx, "connDialRawConn", logdial).Done()
110

111 112 113
	// make a copy of the manet.Dialer, we may need to change its timeout.
	madialer := d.Dialer

114
	if laddr != nil && reuseport.Available() {
115 116 117 118
		// 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
119 120
		// dial using reuseport.Dialer, because we're probably reusing addrs.
		// this is optimistic, as the reuseDial may fail to bind the port.
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
121
		rpev := log.EventBegin(ctx, "connDialReusePort", logdial)
122
		if nconn, retry, reuseErr := reuseDial(madialer.Dialer, laddr, raddr); reuseErr == nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
123
			// if it worked, wrap the raw net.Conn with our manet.Conn
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
124 125
			logdial["reuseport"] = "success"
			rpev.Done()
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
126
			return manet.WrapNetConn(nconn)
127 128
		} else if !retry {
			// reuseDial is sure this is a legitimate dial failure, not a reuseport failure.
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
129 130 131
			logdial["reuseport"] = "failure"
			logdial["error"] = reuseErr
			rpev.Done()
132
			return nil, reuseErr
133
		} else {
134
			// this is a failure to reuse port. log it.
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
135 136 137
			logdial["reuseport"] = "retry"
			logdial["error"] = reuseErr
			rpev.Done()
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
138 139 140
		}
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
141
	defer log.EventBegin(ctx, "connDialManet", logdial).Done()
142
	return madialer.Dial(raddr)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
143 144
}

145
func reuseDial(dialer net.Dialer, laddr, raddr ma.Multiaddr) (conn net.Conn, retry bool, err error) {
146 147 148 149 150
	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
151 152
	// give reuse.Dialer the manet.Dialer's Dialer.
	// (wow, Dialer should've so been an interface...)
153
	rd := reuseport.Dialer{dialer}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
154 155 156 157

	// get the local net.Addr manually
	rd.D.LocalAddr, err = manet.ToNetAddr(laddr)
	if err != nil {
158
		return nil, true, err // something wrong with laddr. retry without.
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
159 160 161 162 163
	}

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

	// rd.Dial gets us a net.Conn with SO_REUSEPORT and SO_REUSEADDR set.
168 169 170 171 172 173 174 175 176 177 178 179
	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.
	}

180 181
	// if it's a network timeout error, it's a legitimate failure.
	if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
182
		return false
183 184
	}

185 186 187 188 189 190 191 192 193 194 195 196 197
	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
198 199 200 201 202 203 204
}

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

205
	// make sure that we ONLY use local addrs that match the remote addr.
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
206 207 208 209 210
	laddrs = manet.AddrMatch(raddr, laddrs)
	if len(laddrs) < 1 {
		return nil
	}

211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228
	// 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
229 230 231 232 233 234
	// 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))]
}

235 236 237 238 239 240 241 242 243 244
// 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
245
		if api.Code != bp[i].Code {
246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261
			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
}