swarm_dial.go 14.8 KB
Newer Older
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
1 2 3
package swarm

import (
4
	"context"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
5 6 7 8 9
	"errors"
	"fmt"
	"sync"
	"time"

10 11 12 13 14
	"github.com/libp2p/go-libp2p-core/network"
	"github.com/libp2p/go-libp2p-core/peer"
	"github.com/libp2p/go-libp2p-core/transport"
	lgbl "github.com/libp2p/go-libp2p-loggables"

Steven Allen's avatar
Steven Allen committed
15
	logging "github.com/ipfs/go-log"
Jeromy's avatar
Jeromy committed
16 17
	addrutil "github.com/libp2p/go-addr-util"
	ma "github.com/multiformats/go-multiaddr"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
18 19 20 21 22 23 24 25 26 27 28 29 30 31
)

// Diagram of dial sync:
//
//   many callers of Dial()   synched w.  dials many addrs       results to callers
//  ----------------------\    dialsync    use earliest            /--------------
//  -----------------------\              |----------\           /----------------
//  ------------------------>------------<-------     >---------<-----------------
//  -----------------------|              \----x                 \----------------
//  ----------------------|                \-----x                \---------------
//                                         any may fail          if no addr at end
//                                                             retry dialAttempt x

var (
32 33
	// ErrDialBackoff is returned by the backoff code when a given peer has
	// been dialed too frequently
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
34
	ErrDialBackoff = errors.New("dial backoff")
35 36 37

	// ErrDialToSelf is returned if we attempt to dial our own peer
	ErrDialToSelf = errors.New("dial to self attempted")
Steven Allen's avatar
Steven Allen committed
38 39 40 41

	// ErrNoTransport is returned when we don't know a transport for the
	// given multiaddr.
	ErrNoTransport = errors.New("no transport for protocol")
42 43 44 45 46 47 48 49

	// ErrAllDialsFailed is returned when connecting to a peer has ultimately failed
	ErrAllDialsFailed = errors.New("all dials failed")

	// ErrNoAddresses is returned when we fail to find any addresses for a
	// peer we're trying to dial.
	ErrNoAddresses = errors.New("no addresses")

Aliabbas Merchant's avatar
Aliabbas Merchant committed
50
	// ErrNoGoodAddresses is returned when we find addresses for a peer but
51 52
	// can't use any of them.
	ErrNoGoodAddresses = errors.New("no good addresses")
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
53 54
)

Steven Allen's avatar
Steven Allen committed
55
// DialAttempts governs how many times a goroutine will try to dial a given peer.
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
56 57
// Note: this is down to one, as we have _too many dials_ atm. To add back in,
// add loop back in Dial(.)
Steven Allen's avatar
Steven Allen committed
58
const DialAttempts = 1
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
59

Steven Allen's avatar
Steven Allen committed
60 61 62
// ConcurrentFdDials is the number of concurrent outbound dials over transports
// that consume file descriptors
const ConcurrentFdDials = 160
Jeromy's avatar
Jeromy committed
63

Steven Allen's avatar
Steven Allen committed
64 65 66
// DefaultPerPeerRateLimit is the number of concurrent outbound dials to make
// per peer
const DefaultPerPeerRateLimit = 8
Jeromy's avatar
Jeromy committed
67

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
// dialbackoff is a struct used to avoid over-dialing the same, dead peers.
// Whenever we totally time out on a peer (all three attempts), we add them
// to dialbackoff. Then, whenevers goroutines would _wait_ (dialsync), they
// check dialbackoff. If it's there, they don't wait and exit promptly with
// an error. (the single goroutine that is actually dialing continues to
// dial). If a dial is successful, the peer is removed from backoff.
// Example:
//
//  for {
//  	if ok, wait := dialsync.Lock(p); !ok {
//  		if backoff.Backoff(p) {
//  			return errDialFailed
//  		}
//  		<-wait
//  		continue
//  	}
//  	defer dialsync.Unlock(p)
//  	c, err := actuallyDial(p)
//  	if err != nil {
//  		dialbackoff.AddBackoff(p)
//  		continue
//  	}
//  	dialbackoff.Clear(p)
//  }
//
Jeromy's avatar
Jeromy committed
93

Steven Allen's avatar
Steven Allen committed
94 95
// DialBackoff is a type for tracking peer dial backoffs.
//
96
// * It's safe to use its zero value.
Steven Allen's avatar
Steven Allen committed
97 98 99
// * It's thread-safe.
// * It's *not* safe to move this type after using.
type DialBackoff struct {
Will Scott's avatar
Will Scott committed
100
	entries map[peer.ID]map[string]*backoffAddr
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
101 102 103
	lock    sync.RWMutex
}

Will Scott's avatar
Will Scott committed
104
type backoffAddr struct {
Jeromy's avatar
Jeromy committed
105 106 107 108
	tries int
	until time.Time
}

Will Scott's avatar
Will Scott committed
109
func (db *DialBackoff) init(ctx context.Context) {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
110
	if db.entries == nil {
Will Scott's avatar
Will Scott committed
111
		db.entries = make(map[peer.ID]map[string]*backoffAddr)
Will Scott's avatar
Will Scott committed
112 113 114 115 116 117
	}
	go db.background(ctx)
}

func (db *DialBackoff) background(ctx context.Context) {
	ticker := time.NewTicker(BackoffMax)
Will Scott's avatar
Will Scott committed
118
	defer ticker.Stop()
Will Scott's avatar
Will Scott committed
119 120 121 122 123 124 125
	for {
		select {
		case <-ctx.Done():
			return
		case <-ticker.C:
			db.cleanup()
		}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
126 127 128 129
	}
}

// Backoff returns whether the client should backoff from dialing
Will Scott's avatar
Will Scott committed
130 131
// peer p at address addr
func (db *DialBackoff) Backoff(p peer.ID, addr ma.Multiaddr) (backoff bool) {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
132
	db.lock.Lock()
Jeromy's avatar
Jeromy committed
133 134
	defer db.lock.Unlock()
	bp, found := db.entries[p]
Will Scott's avatar
Will Scott committed
135 136
	if found {
		ap, found := bp[string(addr.Bytes())]
Will Scott's avatar
Will Scott committed
137 138 139
		if found && time.Now().Before(ap.until) {
			return true
		}
Jeromy's avatar
Jeromy committed
140 141 142
	}

	return false
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
143 144
}

Steven Allen's avatar
Steven Allen committed
145 146 147 148 149 150 151 152
// BackoffBase is the base amount of time to backoff (default: 5s).
var BackoffBase = time.Second * 5

// BackoffCoef is the backoff coefficient (default: 1s).
var BackoffCoef = time.Second

// BackoffMax is the maximum backoff time (default: 5m).
var BackoffMax = time.Minute * 5
Jeromy's avatar
Jeromy committed
153

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
154 155 156
// AddBackoff lets other nodes know that we've entered backoff with
// peer p, so dialers should not wait unnecessarily. We still will
// attempt to dial with one goroutine, in case we get through.
Steven Allen's avatar
Steven Allen committed
157 158 159 160 161 162 163
//
// Backoff is not exponential, it's quadratic and computed according to the
// following formula:
//
//     BackoffBase + BakoffCoef * PriorBackoffs^2
//
// Where PriorBackoffs is the number of previous backoffs.
Will Scott's avatar
Will Scott committed
164
func (db *DialBackoff) AddBackoff(p peer.ID, addr ma.Multiaddr) {
Will Scott's avatar
Will Scott committed
165
	saddr := string(addr.Bytes())
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
166
	db.lock.Lock()
Jeromy's avatar
Jeromy committed
167 168 169
	defer db.lock.Unlock()
	bp, ok := db.entries[p]
	if !ok {
Will Scott's avatar
Will Scott committed
170 171
		db.entries[p] = make(map[string]*backoffAddr)
		db.entries[p][saddr] = &backoffAddr{
Will Scott's avatar
Will Scott committed
172 173 174 175 176
			tries: 1,
			until: time.Now().Add(BackoffBase),
		}
		return
	}
Will Scott's avatar
Will Scott committed
177
	ba, ok := bp[saddr]
Will Scott's avatar
Will Scott committed
178
	if !ok {
Will Scott's avatar
Will Scott committed
179
		bp[saddr] = &backoffAddr{
Jeromy's avatar
Jeromy committed
180
			tries: 1,
Steven Allen's avatar
Steven Allen committed
181
			until: time.Now().Add(BackoffBase),
Jeromy's avatar
Jeromy committed
182 183 184 185
		}
		return
	}

Will Scott's avatar
Will Scott committed
186
	backoffTime := BackoffBase + BackoffCoef*time.Duration(ba.tries*ba.tries)
Steven Allen's avatar
Steven Allen committed
187 188
	if backoffTime > BackoffMax {
		backoffTime = BackoffMax
Jeromy's avatar
Jeromy committed
189
	}
Will Scott's avatar
Will Scott committed
190 191
	ba.until = time.Now().Add(backoffTime)
	ba.tries++
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
192 193 194 195
}

// Clear removes a backoff record. Clients should call this after a
// successful Dial.
Steven Allen's avatar
Steven Allen committed
196
func (db *DialBackoff) Clear(p peer.ID) {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
197
	db.lock.Lock()
Jeromy's avatar
Jeromy committed
198
	defer db.lock.Unlock()
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
199 200 201
	delete(db.entries, p)
}

Will Scott's avatar
Will Scott committed
202 203 204 205 206 207 208 209 210 211 212 213 214
func (db *DialBackoff) cleanup() {
	db.lock.Lock()
	defer db.lock.Unlock()
	now := time.Now()
	for p, e := range db.entries {
		good := false
		for _, backoff := range e {
			if now.Before(backoff.until) {
				good = true
				break
			}
		}
		if !good {
Will Scott's avatar
Will Scott committed
215
			delete(db.entries, p)
Will Scott's avatar
Will Scott committed
216 217 218 219
		}
	}
}

Steven Allen's avatar
Steven Allen committed
220
// DialPeer connects to a peer.
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
221 222 223 224
//
// The idea is that the client of Swarm does not need to know what network
// the connection will happen over. Swarm can use whichever it choses.
// This allows us to use various transport protocols, do NAT traversal/relay,
Steven Allen's avatar
Steven Allen committed
225
// etc. to achieve connection.
226
func (s *Swarm) DialPeer(ctx context.Context, p peer.ID) (network.Conn, error) {
Steven Allen's avatar
Steven Allen committed
227 228 229 230 231 232 233 234 235
	return s.dialPeer(ctx, p)
}

// internal dial method that returns an unwrapped conn
//
// It is gated by the swarm's dial synchronization systems: dialsync and
// dialbackoff.
func (s *Swarm) dialPeer(ctx context.Context, p peer.ID) (*Conn, error) {
	log.Debugf("[%s] swarm dialing peer [%s]", s.local, p)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
236
	var logdial = lgbl.Dial("swarm", s.LocalPeer(), p, nil, nil)
237 238 239 240 241
	err := p.Validate()
	if err != nil {
		return nil, err
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
242 243 244 245 246
	if p == s.local {
		log.Event(ctx, "swarmDialSelf", logdial)
		return nil, ErrDialToSelf
	}

247
	defer log.EventBegin(ctx, "swarmDialAttemptSync", p).Done()
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
248 249

	// check if we already have an open connection first
Steven Allen's avatar
Steven Allen committed
250
	conn := s.bestConnToPeer(p)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
251 252 253 254
	if conn != nil {
		return conn, nil
	}

255
	// apply the DialPeer timeout
256
	ctx, cancel := context.WithTimeout(ctx, network.GetDialPeerTimeout(ctx))
257 258
	defer cancel()

259
	conn, err = s.dsync.DialLock(ctx, p)
260 261
	if err == nil {
		return conn, nil
Steven Allen's avatar
Steven Allen committed
262
	}
263

Steven Allen's avatar
Steven Allen committed
264
	log.Debugf("network for %s finished dialing %s", s.local, p)
265 266 267 268 269 270 271 272 273 274 275 276

	if ctx.Err() != nil {
		// Context error trumps any dial errors as it was likely the ultimate cause.
		return nil, ctx.Err()
	}

	if s.ctx.Err() != nil {
		// Ok, so the swarm is shutting down.
		return nil, ErrSwarmClosed
	}

	return nil, err
277
}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
278

279 280 281
// doDial is an ugly shim method to retain all the logging and backoff logic
// of the old dialsync code
func (s *Swarm) doDial(ctx context.Context, p peer.ID) (*Conn, error) {
Steven Allen's avatar
Steven Allen committed
282 283 284 285 286 287 288 289
	// Short circuit.
	// By the time we take the dial lock, we may already *have* a connection
	// to the peer.
	c := s.bestConnToPeer(p)
	if c != nil {
		return c, nil
	}

Steven Allen's avatar
Steven Allen committed
290 291
	logdial := lgbl.Dial("swarm", s.LocalPeer(), p, nil, nil)

292 293 294
	// ok, we have been charged to dial! let's do it.
	// if it succeeds, dial will add the conn to the swarm itself.
	defer log.EventBegin(ctx, "swarmDialAttemptStart", logdial).Done()
Steven Allen's avatar
Steven Allen committed
295 296

	conn, err := s.dial(ctx, p)
297
	if err != nil {
Steven Allen's avatar
Steven Allen committed
298 299 300 301 302 303 304 305 306
		conn = s.bestConnToPeer(p)
		if conn != nil {
			// Hm? What error?
			// Could have canceled the dial because we received a
			// connection or some other random reason.
			// Just ignore the error and return the connection.
			log.Debugf("ignoring dial error because we have a connection: %s", err)
			return conn, nil
		}
307

Steven Allen's avatar
Steven Allen committed
308
		// ok, we failed.
309
		return nil, err
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
310
	}
311
	return conn, nil
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
312 313
}

Steven Allen's avatar
Steven Allen committed
314 315 316 317 318
func (s *Swarm) canDial(addr ma.Multiaddr) bool {
	t := s.TransportForDialing(addr)
	return t != nil && t.CanDial(addr)
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
319 320 321 322 323 324 325 326 327 328 329
// dial is the actual swarm's dial logic, gated by Dial.
func (s *Swarm) dial(ctx context.Context, p peer.ID) (*Conn, error) {
	var logdial = lgbl.Dial("swarm", s.LocalPeer(), p, nil, nil)
	if p == s.local {
		log.Event(ctx, "swarmDialDoDialSelf", logdial)
		return nil, ErrDialToSelf
	}
	defer log.EventBegin(ctx, "swarmDialDo", logdial).Done()
	logdial["dial"] = "failure" // start off with failure. set to "success" at the end.

	sk := s.peers.PrivKey(s.local)
Matt Joiner's avatar
Matt Joiner committed
330
	logdial["encrypted"] = sk != nil // log whether this will be an encrypted dial or not.
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
331 332 333 334 335
	if sk == nil {
		// fine for sk to be nil, just log.
		log.Debug("Dial not given PrivateKey, so WILL NOT SECURE conn.")
	}

Jeromy's avatar
Jeromy committed
336 337
	//////
	/*
338
		This slice-to-chan code is temporary, the peerstore can currently provide
Jeromy's avatar
Jeromy committed
339 340 341 342 343
		a channel as an interface for receiving addresses, but more thought
		needs to be put into the execution. For now, this allows us to use
		the improved rate limiter, while maintaining the outward behaviour
		that we previously had (halting a dial when we run out of addrs)
	*/
Matt Joiner's avatar
Matt Joiner committed
344 345
	peerAddrs := s.peers.Addrs(p)
	if len(peerAddrs) == 0 {
346
		return nil, &DialError{Peer: p, Cause: ErrNoAddresses}
Matt Joiner's avatar
Matt Joiner committed
347 348
	}
	goodAddrs := s.filterKnownUndialables(peerAddrs)
Matt Joiner's avatar
Matt Joiner committed
349
	if len(goodAddrs) == 0 {
350
		return nil, &DialError{Peer: p, Cause: ErrNoGoodAddresses}
Matt Joiner's avatar
Matt Joiner committed
351
	}
352
	goodAddrsChan := make(chan ma.Multiaddr, len(goodAddrs))
Will Scott's avatar
Will Scott committed
353
	nonBackoff := false
354
	for _, a := range goodAddrs {
Will Scott's avatar
Will Scott committed
355 356 357 358 359
		// skip addresses in back-off
		if !s.backf.Backoff(p, a) {
			nonBackoff = true
			goodAddrsChan <- a
		}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
360
	}
361
	close(goodAddrsChan)
Will Scott's avatar
Will Scott committed
362 363 364
	if !nonBackoff {
		return nil, ErrDialBackoff
	}
Jeromy's avatar
Jeromy committed
365
	/////////
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
366 367

	// try to get a connection to any addr
368 369 370
	connC, dialErr := s.dialAddrs(ctx, p, goodAddrsChan)
	if dialErr != nil {
		logdial["error"] = dialErr.Cause.Error()
371 372 373 374
		switch dialErr.Cause {
		case context.Canceled, context.DeadlineExceeded:
			// Always prefer the context errors as we rely on being
			// able to check them.
375 376 377
			//
			// Removing this will BREAK backoff (causing us to
			// backoff when canceling dials).
378
			return nil, dialErr.Cause
379 380
		}
		return nil, dialErr
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
381
	}
Steven Allen's avatar
Steven Allen committed
382 383 384 385
	logdial["conn"] = logging.Metadata{
		"localAddr":  connC.LocalMultiaddr(),
		"remoteAddr": connC.RemoteMultiaddr(),
	}
386
	swarmC, err := s.addConn(connC, network.DirOutbound)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
387
	if err != nil {
Jeromy's avatar
Jeromy committed
388
		logdial["error"] = err.Error()
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
389
		connC.Close() // close the connection. didn't work out :(
390
		return nil, &DialError{Peer: p, Cause: err}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
391 392 393 394 395 396
	}

	logdial["dial"] = "success"
	return swarmC, nil
}

397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421
// filterKnownUndialables takes a list of multiaddrs, and removes those
// that we definitely don't want to dial: addresses configured to be blocked,
// IPv6 link-local addresses, addresses without a dial-capable transport,
// and addresses that we know to be our own.
// This is an optimization to avoid wasting time on dials that we know are going to fail.
func (s *Swarm) filterKnownUndialables(addrs []ma.Multiaddr) []ma.Multiaddr {
	lisAddrs, _ := s.InterfaceListenAddresses()
	var ourAddrs []ma.Multiaddr
	for _, addr := range lisAddrs {
		protos := addr.Protocols()
		// we're only sure about filtering out /ip4 and /ip6 addresses, so far
		if len(protos) == 2 && (protos[0].Code == ma.P_IP4 || protos[0].Code == ma.P_IP6) {
			ourAddrs = append(ourAddrs, addr)
		}
	}

	return addrutil.FilterAddrs(addrs,
		addrutil.SubtractFilter(ourAddrs...),
		s.canDial,
		// TODO: Consider allowing link-local addresses
		addrutil.AddrOverNonLocalIP,
		addrutil.FilterNeg(s.Filters.AddrBlocked),
	)
}

422
func (s *Swarm) dialAddrs(ctx context.Context, p peer.ID, remoteAddrs <-chan ma.Multiaddr) (transport.CapableConn, *DialError) {
Jeromy's avatar
Jeromy committed
423
	log.Debugf("%s swarm dialing %s", s.local, p)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
424 425 426 427

	ctx, cancel := context.WithCancel(ctx)
	defer cancel() // cancel work when we exit func

Jeromy's avatar
Jeromy committed
428 429
	// use a single response type instead of errs and conns, reduces complexity *a ton*
	respch := make(chan dialResult)
430
	err := &DialError{Peer: p}
Jeromy's avatar
Jeromy committed
431

432 433
	defer s.limiter.clearAllPeerDials(p)

Jeromy's avatar
Jeromy committed
434
	var active int
435
dialLoop:
436 437 438 439
	for remoteAddrs != nil || active > 0 {
		// Check for context cancellations and/or responses first.
		select {
		case <-ctx.Done():
440
			break dialLoop
441 442 443 444
		case resp := <-respch:
			active--
			if resp.Err != nil {
				// Errors are normal, lots of dials will fail
Will Scott's avatar
Will Scott committed
445 446 447 448
				if resp.Err != context.Canceled {
					s.backf.AddBackoff(p, resp.Addr)
				}

tg's avatar
tg committed
449
				log.Infof("got error on dial: %s", resp.Err)
450
				err.recordErr(resp.Addr, resp.Err)
451 452 453 454 455 456 457 458 459 460
			} else if resp.Conn != nil {
				return resp.Conn, nil
			}

			// We got a result, try again from the top.
			continue
		default:
		}

		// Now, attempt to dial.
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
461
		select {
Jeromy's avatar
Jeromy committed
462 463 464 465
		case addr, ok := <-remoteAddrs:
			if !ok {
				remoteAddrs = nil
				continue
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
466 467
			}

Jeromy's avatar
Jeromy committed
468 469 470
			s.limitedDial(ctx, p, addr, respch)
			active++
		case <-ctx.Done():
471
			break dialLoop
Jeromy's avatar
Jeromy committed
472 473 474 475
		case resp := <-respch:
			active--
			if resp.Err != nil {
				// Errors are normal, lots of dials will fail
Will Scott's avatar
Will Scott committed
476 477 478 479
				if resp.Err != context.Canceled {
					s.backf.AddBackoff(p, resp.Addr)
				}

tg's avatar
tg committed
480
				log.Infof("got error on dial: %s", resp.Err)
481
				err.recordErr(resp.Addr, resp.Err)
Jeromy's avatar
Jeromy committed
482 483
			} else if resp.Conn != nil {
				return resp.Conn, nil
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
484 485 486
			}
		}
	}
tg's avatar
tg committed
487

488 489 490
	if ctxErr := ctx.Err(); ctxErr != nil {
		err.Cause = ctxErr
	} else if len(err.DialErrors) == 0 {
491
		err.Cause = network.ErrNoRemoteAddrs
492 493
	} else {
		err.Cause = ErrAllDialsFailed
tg's avatar
tg committed
494
	}
495
	return nil, err
Jeromy's avatar
Jeromy committed
496 497
}

Jeromy's avatar
Jeromy committed
498 499 500
// limitedDial will start a dial to the given peer when
// it is able, respecting the various different types of rate
// limiting that occur without using extra goroutines per addr
Jeromy's avatar
Jeromy committed
501 502 503 504 505 506 507
func (s *Swarm) limitedDial(ctx context.Context, p peer.ID, a ma.Multiaddr, resp chan dialResult) {
	s.limiter.AddDialJob(&dialJob{
		addr: a,
		peer: p,
		resp: resp,
		ctx:  ctx,
	})
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
508 509
}

510
func (s *Swarm) dialAddr(ctx context.Context, p peer.ID, addr ma.Multiaddr) (transport.CapableConn, error) {
Steven Allen's avatar
Steven Allen committed
511 512 513 514
	// Just to double check. Costs nothing.
	if s.local == p {
		return nil, ErrDialToSelf
	}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
515 516
	log.Debugf("%s swarm dialing %s %s", s.local, p, addr)

517 518
	tpt := s.TransportForDialing(addr)
	if tpt == nil {
Steven Allen's avatar
Steven Allen committed
519
		return nil, ErrNoTransport
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
520 521
	}

522
	connC, err := tpt.Dial(ctx, addr, p)
Steven Allen's avatar
Steven Allen committed
523
	if err != nil {
524
		return nil, err
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
525 526
	}

Steven Allen's avatar
Steven Allen committed
527 528
	// Trust the transport? Yeah... right.
	if connC.RemotePeer() != p {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
529
		connC.Close()
530
		err = fmt.Errorf("BUG in transport %T: tried to dial %s, dialed %s", p, connC.RemotePeer(), tpt)
Steven Allen's avatar
Steven Allen committed
531 532
		log.Error(err)
		return nil, err
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
533 534 535 536 537
	}

	// success! we got one!
	return connC, nil
}