swarm_dial.go 14.4 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 {
Jeromy's avatar
Jeromy committed
100
	entries map[peer.ID]*backoffPeer
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
101 102 103
	lock    sync.RWMutex
}

Will Scott's avatar
Will Scott committed
104 105 106
type backoffPeer map[ma.Multiaddr]*backoffAddr

type backoffAddr struct {
Jeromy's avatar
Jeromy committed
107 108 109 110
	tries int
	until time.Time
}

Steven Allen's avatar
Steven Allen committed
111
func (db *DialBackoff) init() {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
112
	if db.entries == nil {
Jeromy's avatar
Jeromy committed
113
		db.entries = make(map[peer.ID]*backoffPeer)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
114 115 116 117
	}
}

// Backoff returns whether the client should backoff from dialing
Will Scott's avatar
Will Scott committed
118 119
// 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
120
	db.lock.Lock()
Jeromy's avatar
Jeromy committed
121
	defer db.lock.Unlock()
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
122
	db.init()
Jeromy's avatar
Jeromy committed
123
	bp, found := db.entries[p]
Will Scott's avatar
Will Scott committed
124 125 126 127 128 129
	if found && bp != nil {
		ap, found := (*bp)[addr]
		// TODO: cleanup out of date entries.
		if found && time.Now().Before(ap.until) {
			return true
		}
Jeromy's avatar
Jeromy committed
130 131 132
	}

	return false
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
133 134
}

Steven Allen's avatar
Steven Allen committed
135 136 137 138 139 140 141 142
// 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
143

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
144 145 146
// 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
147 148 149 150 151 152 153
//
// 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
154
func (db *DialBackoff) AddBackoff(p peer.ID, addr ma.Multiaddr) {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
155
	db.lock.Lock()
Jeromy's avatar
Jeromy committed
156
	defer db.lock.Unlock()
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
157
	db.init()
Jeromy's avatar
Jeromy committed
158 159
	bp, ok := db.entries[p]
	if !ok {
Will Scott's avatar
Will Scott committed
160 161 162 163 164 165 166 167 168 169 170 171
		bp := backoffPeer(make(map[ma.Multiaddr]*backoffAddr))
		db.entries[p] = &bp
		bp[addr] = &backoffAddr{
			tries: 1,
			until: time.Now().Add(BackoffBase),
		}
		return
	}
	// todo: cleanup out of date entries.
	ba, ok := (*bp)[addr]
	if !ok {
		(*bp)[addr] = &backoffAddr{
Jeromy's avatar
Jeromy committed
172
			tries: 1,
Steven Allen's avatar
Steven Allen committed
173
			until: time.Now().Add(BackoffBase),
Jeromy's avatar
Jeromy committed
174 175 176 177
		}
		return
	}

Will Scott's avatar
Will Scott committed
178
	backoffTime := BackoffBase + BackoffCoef*time.Duration(ba.tries*ba.tries)
Steven Allen's avatar
Steven Allen committed
179 180
	if backoffTime > BackoffMax {
		backoffTime = BackoffMax
Jeromy's avatar
Jeromy committed
181
	}
Will Scott's avatar
Will Scott committed
182 183
	ba.until = time.Now().Add(backoffTime)
	ba.tries++
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
184 185 186 187
}

// Clear removes a backoff record. Clients should call this after a
// successful Dial.
Steven Allen's avatar
Steven Allen committed
188
func (db *DialBackoff) Clear(p peer.ID) {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
189
	db.lock.Lock()
Jeromy's avatar
Jeromy committed
190
	defer db.lock.Unlock()
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
191 192 193 194
	db.init()
	delete(db.entries, p)
}

Steven Allen's avatar
Steven Allen committed
195
// DialPeer connects to a peer.
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
196 197 198 199
//
// 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
200
// etc. to achieve connection.
201
func (s *Swarm) DialPeer(ctx context.Context, p peer.ID) (network.Conn, error) {
Steven Allen's avatar
Steven Allen committed
202 203 204 205 206 207 208 209 210
	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
211
	var logdial = lgbl.Dial("swarm", s.LocalPeer(), p, nil, nil)
212 213 214 215 216
	err := p.Validate()
	if err != nil {
		return nil, err
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
217 218 219 220 221
	if p == s.local {
		log.Event(ctx, "swarmDialSelf", logdial)
		return nil, ErrDialToSelf
	}

222
	defer log.EventBegin(ctx, "swarmDialAttemptSync", p).Done()
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
223 224

	// check if we already have an open connection first
Steven Allen's avatar
Steven Allen committed
225
	conn := s.bestConnToPeer(p)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
226 227 228 229
	if conn != nil {
		return conn, nil
	}

230
	// apply the DialPeer timeout
231
	ctx, cancel := context.WithTimeout(ctx, network.GetDialPeerTimeout(ctx))
232 233
	defer cancel()

234
	conn, err = s.dsync.DialLock(ctx, p)
235 236
	if err == nil {
		return conn, nil
Steven Allen's avatar
Steven Allen committed
237
	}
238

Steven Allen's avatar
Steven Allen committed
239
	log.Debugf("network for %s finished dialing %s", s.local, p)
240 241 242 243 244 245 246 247 248 249 250 251

	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
252
}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
253

254 255 256
// 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
257 258 259 260 261 262 263 264
	// 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
265 266
	logdial := lgbl.Dial("swarm", s.LocalPeer(), p, nil, nil)

267 268 269
	// 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
270 271

	conn, err := s.dial(ctx, p)
272
	if err != nil {
Steven Allen's avatar
Steven Allen committed
273 274 275 276 277 278 279 280 281
		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
		}
282

Steven Allen's avatar
Steven Allen committed
283
		// ok, we failed.
284
		return nil, err
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
285
	}
286
	return conn, nil
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
287 288
}

Steven Allen's avatar
Steven Allen committed
289 290 291 292 293
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
294 295 296 297 298 299 300 301 302 303 304
// 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
305
	logdial["encrypted"] = sk != nil // log whether this will be an encrypted dial or not.
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
306 307 308 309 310
	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
311 312
	//////
	/*
313
		This slice-to-chan code is temporary, the peerstore can currently provide
Jeromy's avatar
Jeromy committed
314 315 316 317 318
		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
319 320
	peerAddrs := s.peers.Addrs(p)
	if len(peerAddrs) == 0 {
321
		return nil, &DialError{Peer: p, Cause: ErrNoAddresses}
Matt Joiner's avatar
Matt Joiner committed
322 323
	}
	goodAddrs := s.filterKnownUndialables(peerAddrs)
Matt Joiner's avatar
Matt Joiner committed
324
	if len(goodAddrs) == 0 {
325
		return nil, &DialError{Peer: p, Cause: ErrNoGoodAddresses}
Matt Joiner's avatar
Matt Joiner committed
326
	}
327
	goodAddrsChan := make(chan ma.Multiaddr, len(goodAddrs))
Will Scott's avatar
Will Scott committed
328
	nonBackoff := false
329
	for _, a := range goodAddrs {
Will Scott's avatar
Will Scott committed
330 331 332 333 334
		// skip addresses in back-off
		if !s.backf.Backoff(p, a) {
			nonBackoff = true
			goodAddrsChan <- a
		}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
335
	}
336
	close(goodAddrsChan)
Will Scott's avatar
Will Scott committed
337 338 339
	if !nonBackoff {
		return nil, ErrDialBackoff
	}
Jeromy's avatar
Jeromy committed
340
	/////////
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
341 342

	// try to get a connection to any addr
343 344 345
	connC, dialErr := s.dialAddrs(ctx, p, goodAddrsChan)
	if dialErr != nil {
		logdial["error"] = dialErr.Cause.Error()
346 347 348 349
		switch dialErr.Cause {
		case context.Canceled, context.DeadlineExceeded:
			// Always prefer the context errors as we rely on being
			// able to check them.
350 351 352
			//
			// Removing this will BREAK backoff (causing us to
			// backoff when canceling dials).
353
			return nil, dialErr.Cause
354 355
		}
		return nil, dialErr
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
356
	}
Steven Allen's avatar
Steven Allen committed
357 358 359 360
	logdial["conn"] = logging.Metadata{
		"localAddr":  connC.LocalMultiaddr(),
		"remoteAddr": connC.RemoteMultiaddr(),
	}
361
	swarmC, err := s.addConn(connC, network.DirOutbound)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
362
	if err != nil {
Jeromy's avatar
Jeromy committed
363
		logdial["error"] = err.Error()
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
364
		connC.Close() // close the connection. didn't work out :(
365
		return nil, &DialError{Peer: p, Cause: err}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
366 367 368 369 370 371
	}

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

372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396
// 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),
	)
}

397
func (s *Swarm) dialAddrs(ctx context.Context, p peer.ID, remoteAddrs <-chan ma.Multiaddr) (transport.CapableConn, *DialError) {
Jeromy's avatar
Jeromy committed
398
	log.Debugf("%s swarm dialing %s", s.local, p)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
399 400 401 402

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

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

407 408
	defer s.limiter.clearAllPeerDials(p)

Jeromy's avatar
Jeromy committed
409
	var active int
410
dialLoop:
411 412 413 414
	for remoteAddrs != nil || active > 0 {
		// Check for context cancellations and/or responses first.
		select {
		case <-ctx.Done():
415
			break dialLoop
416 417 418 419
		case resp := <-respch:
			active--
			if resp.Err != nil {
				// Errors are normal, lots of dials will fail
Will Scott's avatar
Will Scott committed
420 421 422 423
				if resp.Err != context.Canceled {
					s.backf.AddBackoff(p, resp.Addr)
				}

tg's avatar
tg committed
424
				log.Infof("got error on dial: %s", resp.Err)
425
				err.recordErr(resp.Addr, resp.Err)
426 427 428 429 430 431 432 433 434 435
			} 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
436
		select {
Jeromy's avatar
Jeromy committed
437 438 439 440
		case addr, ok := <-remoteAddrs:
			if !ok {
				remoteAddrs = nil
				continue
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
441 442
			}

Jeromy's avatar
Jeromy committed
443 444 445
			s.limitedDial(ctx, p, addr, respch)
			active++
		case <-ctx.Done():
446
			break dialLoop
Jeromy's avatar
Jeromy committed
447 448 449 450
		case resp := <-respch:
			active--
			if resp.Err != nil {
				// Errors are normal, lots of dials will fail
Will Scott's avatar
Will Scott committed
451 452 453 454
				if resp.Err != context.Canceled {
					s.backf.AddBackoff(p, resp.Addr)
				}

tg's avatar
tg committed
455
				log.Infof("got error on dial: %s", resp.Err)
456
				err.recordErr(resp.Addr, resp.Err)
Jeromy's avatar
Jeromy committed
457 458
			} else if resp.Conn != nil {
				return resp.Conn, nil
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
459 460 461
			}
		}
	}
tg's avatar
tg committed
462

463 464 465
	if ctxErr := ctx.Err(); ctxErr != nil {
		err.Cause = ctxErr
	} else if len(err.DialErrors) == 0 {
466
		err.Cause = network.ErrNoRemoteAddrs
467 468
	} else {
		err.Cause = ErrAllDialsFailed
tg's avatar
tg committed
469
	}
470
	return nil, err
Jeromy's avatar
Jeromy committed
471 472
}

Jeromy's avatar
Jeromy committed
473 474 475
// 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
476 477 478 479 480 481 482
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
483 484
}

485
func (s *Swarm) dialAddr(ctx context.Context, p peer.ID, addr ma.Multiaddr) (transport.CapableConn, error) {
Steven Allen's avatar
Steven Allen committed
486 487 488 489
	// Just to double check. Costs nothing.
	if s.local == p {
		return nil, ErrDialToSelf
	}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
490 491
	log.Debugf("%s swarm dialing %s %s", s.local, p, addr)

492 493
	tpt := s.TransportForDialing(addr)
	if tpt == nil {
Steven Allen's avatar
Steven Allen committed
494
		return nil, ErrNoTransport
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
495 496
	}

497
	connC, err := tpt.Dial(ctx, addr, p)
Steven Allen's avatar
Steven Allen committed
498
	if err != nil {
499
		return nil, err
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
500 501
	}

Steven Allen's avatar
Steven Allen committed
502 503
	// Trust the transport? Yeah... right.
	if connC.RemotePeer() != p {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
504
		connC.Close()
505
		err = fmt.Errorf("BUG in transport %T: tried to dial %s, dialed %s", p, connC.RemotePeer(), tpt)
Steven Allen's avatar
Steven Allen committed
506 507
		log.Error(err)
		return nil, err
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
508 509 510 511 512
	}

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