basic_host.go 14.9 KB
Newer Older
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
1
2
3
package basichost

import (
4
	"context"
5
	"io"
6
	"time"
7

Jeromy's avatar
Jeromy committed
8
9
	identify "github.com/libp2p/go-libp2p/p2p/protocol/identify"

Jeromy's avatar
Jeromy committed
10
11
	logging "github.com/ipfs/go-log"
	goprocess "github.com/jbenet/goprocess"
12
	circuit "github.com/libp2p/go-libp2p-circuit"
Jeromy's avatar
Jeromy committed
13
	connmgr "github.com/libp2p/go-libp2p-connmgr"
Jeromy's avatar
Jeromy committed
14
15
16
	metrics "github.com/libp2p/go-libp2p-metrics"
	mstream "github.com/libp2p/go-libp2p-metrics/stream"
	inet "github.com/libp2p/go-libp2p-net"
Jeromy's avatar
Jeromy committed
17
18
	peer "github.com/libp2p/go-libp2p-peer"
	pstore "github.com/libp2p/go-libp2p-peerstore"
Jeromy's avatar
Jeromy committed
19
	protocol "github.com/libp2p/go-libp2p-protocol"
Jeromy's avatar
Jeromy committed
20
	ma "github.com/multiformats/go-multiaddr"
21
	madns "github.com/multiformats/go-multiaddr-dns"
Jeromy's avatar
Jeromy committed
22
	msmux "github.com/multiformats/go-multistream"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
23
24
)

25
var log = logging.Logger("basichost")
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
26

27
28
29
30
31
32
33
var (
	// DefaultNegotiationTimeout is the default value for HostOpts.NegotiationTimeout.
	DefaultNegotiationTimeout = time.Second * 60

	// DefaultAddrsFactory is the default value for HostOpts.AddrsFactory.
	DefaultAddrsFactory = func(addrs []ma.Multiaddr) []ma.Multiaddr { return addrs }
)
34

35
36
37
38
// AddrsFactory functions can be passed to New in order to override
// addresses returned by Addrs.
type AddrsFactory func([]ma.Multiaddr) []ma.Multiaddr

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
39
// Option is a type used to pass in options to the host.
40
41
//
// Deprecated in favor of HostOpts and NewHost.
42
43
type Option int

44
45
46
47
48
49
50
51
// NATPortMap makes the host attempt to open port-mapping in NAT devices
// for all its listeners. Pass in this option in the constructor to
// asynchronously a) find a gateway, b) open port mappings, c) republish
// port mappings periodically. The NATed addresses are included in the
// Host's Addrs() list.
//
// This option is deprecated in favor of HostOpts and NewHost.
const NATPortMap Option = iota
52

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
53
54
55
56
57
// BasicHost is the basic implementation of the host.Host interface. This
// particular host implementation:
//  * uses a protocol muxer to mux per-protocol streams
//  * uses an identity service to send + receive node information
//  * uses a nat service to establish NAT port mappings
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
58
type BasicHost struct {
59
60
61
62
63
64
65
	network    inet.Network
	mux        *msmux.MultistreamMuxer
	ids        *identify.IDService
	natmgr     NATManager
	addrs      AddrsFactory
	maResolver *madns.Resolver
	cmgr       connmgr.ConnManager
66

67
	negtimeout time.Duration
68

69
	proc goprocess.Process
Jeromy's avatar
Jeromy committed
70
71

	bwc metrics.Reporter
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
72
73
}

74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
// HostOpts holds options that can be passed to NewHost in order to
// customize construction of the *BasicHost.
type HostOpts struct {

	// MultistreamMuxer is essential for the *BasicHost and will use a sensible default value if omitted.
	MultistreamMuxer *msmux.MultistreamMuxer

	// NegotiationTimeout determines the read and write timeouts on streams.
	// If 0 or omitted, it will use DefaultNegotiationTimeout.
	// If below 0, timeouts on streams will be deactivated.
	NegotiationTimeout time.Duration

	// IdentifyService holds an implementation of the /ipfs/id/ protocol.
	// If omitted, a new *identify.IDService will be used.
	IdentifyService *identify.IDService

	// AddrsFactory holds a function which can be used to override or filter the result of Addrs.
	// If omitted, there's no override or filtering, and the results of Addrs and AllAddrs are the same.
	AddrsFactory AddrsFactory

94
95
96
97
	// MultiaddrResolves holds the go-multiaddr-dns.Resolver used for resolving
	// /dns4, /dns6, and /dnsaddr addresses before trying to connect to a peer.
	MultiaddrResolver *madns.Resolver

98
99
	// NATManager takes care of setting NAT port mappings, and discovering external addresses.
	// If omitted, this will simply be disabled.
100
	NATManager NATManager
101

102
103
	// BandwidthReporter is used for collecting aggregate metrics of the
	// bandwidth used by various protocols.
104
	BandwidthReporter metrics.Reporter
Jeromy's avatar
Jeromy committed
105
106
107

	// ConnManager is a libp2p connection manager
	ConnManager connmgr.ConnManager
108
109

	// Relay indicates whether the host should use circuit relay transport
110
	EnableRelay bool
111
112
113

	// RelayOpts are options for the relay transport; only meaningful when Relay=true
	RelayOpts []circuit.RelayOpt
114
115
116
}

// NewHost constructs a new *BasicHost and activates it by attaching its stream and connection handlers to the given inet.Network.
117
func NewHost(ctx context.Context, net inet.Network, opts *HostOpts) (*BasicHost, error) {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
118
	h := &BasicHost{
119
120
121
122
		network:    net,
		mux:        msmux.NewMultistreamMuxer(),
		negtimeout: DefaultNegotiationTimeout,
		addrs:      DefaultAddrsFactory,
123
		maResolver: madns.DefaultResolver,
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
	}

	if opts.MultistreamMuxer != nil {
		h.mux = opts.MultistreamMuxer
	}

	if opts.IdentifyService != nil {
		h.ids = opts.IdentifyService
	} else {
		// we can't set this as a default above because it depends on the *BasicHost.
		h.ids = identify.NewIDService(h)
	}

	if uint64(opts.NegotiationTimeout) != 0 {
		h.negtimeout = opts.NegotiationTimeout
	}

	if opts.AddrsFactory != nil {
		h.addrs = opts.AddrsFactory
	}

	if opts.NATManager != nil {
		h.natmgr = opts.NATManager
	}

149
150
151
152
	if opts.MultiaddrResolver != nil {
		h.maResolver = opts.MultiaddrResolver
	}

153
154
155
	if opts.BandwidthReporter != nil {
		h.bwc = opts.BandwidthReporter
		h.ids.Reporter = opts.BandwidthReporter
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
156
157
	}

Jeromy's avatar
Jeromy committed
158
159
160
161
162
163
164
	if opts.ConnManager == nil {
		// create 'disabled' conn manager for now
		h.cmgr = connmgr.NewConnManager(0, 0, 0)
	} else {
		h.cmgr = opts.ConnManager
	}

165
166
167
	var relayCtx context.Context
	var relayCancel func()

168
	h.proc = goprocess.WithTeardown(func() error {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
169
170
171
		if h.natmgr != nil {
			h.natmgr.Close()
		}
172
173
174
		if relayCancel != nil {
			relayCancel()
		}
175
176
177
		return h.Network().Close()
	})

178
179
180
	net.SetConnHandler(h.newConnHandler)
	net.SetStreamHandler(h.newStreamHandler)

181
	if opts.EnableRelay {
182
		relayCtx, relayCancel = context.WithCancel(ctx)
183
184
185
186
187
188
189
		err := circuit.AddRelayTransport(relayCtx, h, opts.RelayOpts...)
		if err != nil {
			h.Close()
			return nil, err
		}
	}

190
	return h, nil
191
}
Jeromy's avatar
Jeromy committed
192

193
194
195
196
197
// New constructs and sets up a new *BasicHost with given Network and options.
// Three options can be passed: NATPortMap, AddrsFactory, and metrics.Reporter.
// This function is deprecated in favor of NewHost and HostOpts.
func New(net inet.Network, opts ...interface{}) *BasicHost {
	hostopts := &HostOpts{}
198

199
	for _, o := range opts {
Jeromy's avatar
Jeromy committed
200
201
202
203
		switch o := o.(type) {
		case Option:
			switch o {
			case NATPortMap:
204
				hostopts.NATManager = newNatManager(net)
Jeromy's avatar
Jeromy committed
205
206
			}
		case metrics.Reporter:
207
			hostopts.BandwidthReporter = o
208
		case AddrsFactory:
209
			hostopts.AddrsFactory = AddrsFactory(o)
Jeromy's avatar
Jeromy committed
210
211
		case connmgr.ConnManager:
			hostopts.ConnManager = o
212
213
		case *madns.Resolver:
			hostopts.MultiaddrResolver = o
214
215
216
		}
	}

217
	h, err := NewHost(context.Background(), net, hostopts)
218
219
220
221
222
223
224
	if err != nil {
		// this cannot happen with legacy options
		// plus we want to keep the (deprecated) legacy interface unchanged
		panic(err)
	}

	return h
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
225
226
227
228
}

// newConnHandler is the remote-opened conn handler for inet.Network
func (h *BasicHost) newConnHandler(c inet.Conn) {
229
230
231
	// Clear protocols on connecting to new peer to avoid issues caused
	// by misremembering protocols between reconnects
	h.Peerstore().SetProtocols(c.RemotePeer())
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
232
233
234
235
	h.ids.IdentifyConn(c)
}

// newStreamHandler is the remote-opened stream handler for inet.Network
Jeromy's avatar
Jeromy committed
236
// TODO: this feels a bit wonky
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
237
func (h *BasicHost) newStreamHandler(s inet.Stream) {
238
	before := time.Now()
239

240
241
	if h.negtimeout > 0 {
		if err := s.SetDeadline(time.Now().Add(h.negtimeout)); err != nil {
242
243
244
245
246
247
			log.Error("setting stream deadline: ", err)
			s.Close()
			return
		}
	}

248
	lzc, protoID, handle, err := h.Mux().NegotiateLazy(s)
249
	took := time.Now().Sub(before)
Jeromy's avatar
Jeromy committed
250
	if err != nil {
251
		if err == io.EOF {
252
253
254
255
256
			logf := log.Debugf
			if took > time.Second*10 {
				logf = log.Warningf
			}
			logf("protocol EOF: %s (took %s)", s.Conn().RemotePeer(), took)
257
		} else {
258
			log.Warning("protocol mux failed: %s (took %s)", err, took)
259
		}
260
		s.Close()
Jeromy's avatar
Jeromy committed
261
262
		return
	}
263

264
265
266
267
268
	s = &streamWrapper{
		Stream: s,
		rw:     lzc,
	}

269
	if h.negtimeout > 0 {
270
271
272
273
274
275
276
		if err := s.SetDeadline(time.Time{}); err != nil {
			log.Error("resetting stream deadline: ", err)
			s.Close()
			return
		}
	}

277
	s.SetProtocol(protocol.ID(protoID))
Jeromy's avatar
Jeromy committed
278

279
280
281
282
	if h.bwc != nil {
		s = mstream.WrapStream(s, h.bwc)
	}
	log.Debugf("protocol negotiation took %s", took)
Jeromy's avatar
Jeromy committed
283

284
	go handle(protoID, s)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
285
286
287
288
289
290
291
292
}

// ID returns the (local) peer.ID associated with this Host
func (h *BasicHost) ID() peer.ID {
	return h.Network().LocalPeer()
}

// Peerstore returns the Host's repository of Peer Addresses and Keys.
Jeromy's avatar
Jeromy committed
293
func (h *BasicHost) Peerstore() pstore.Peerstore {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
294
295
296
	return h.Network().Peerstore()
}

297
// Network returns the Network interface of the Host
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
298
299
300
301
302
func (h *BasicHost) Network() inet.Network {
	return h.network
}

// Mux returns the Mux multiplexing incoming streams to protocol handlers
Jeromy's avatar
Jeromy committed
303
func (h *BasicHost) Mux() *msmux.MultistreamMuxer {
Brian Tiger Chow's avatar
Brian Tiger Chow committed
304
	return h.mux
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
305
306
}

307
// IDService returns
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
308
309
310
311
312
313
314
315
316
func (h *BasicHost) IDService() *identify.IDService {
	return h.ids
}

// SetStreamHandler sets the protocol handler on the Host's Mux.
// This is equivalent to:
//   host.Mux().SetHandler(proto, handler)
// (Threadsafe)
func (h *BasicHost) SetStreamHandler(pid protocol.ID, handler inet.StreamHandler) {
317
318
	h.Mux().AddHandler(string(pid), func(p string, rwc io.ReadWriteCloser) error {
		is := rwc.(inet.Stream)
319
		is.SetProtocol(protocol.ID(p))
320
		handler(is)
Jeromy's avatar
Jeromy committed
321
322
		return nil
	})
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
323
324
}

325
326
327
328
329
// SetStreamHandlerMatch sets the protocol handler on the Host's Mux
// using a matching function to do protocol comparisons
func (h *BasicHost) SetStreamHandlerMatch(pid protocol.ID, m func(string) bool, handler inet.StreamHandler) {
	h.Mux().AddHandlerWithFunc(string(pid), m, func(p string, rwc io.ReadWriteCloser) error {
		is := rwc.(inet.Stream)
330
		is.SetProtocol(protocol.ID(p))
331
332
333
334
335
		handler(is)
		return nil
	})
}

336
// RemoveStreamHandler returns ..
Jeromy's avatar
Jeromy committed
337
func (h *BasicHost) RemoveStreamHandler(pid protocol.ID) {
Jeromy's avatar
Jeromy committed
338
	h.Mux().RemoveHandler(string(pid))
Jeromy's avatar
Jeromy committed
339
340
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
341
342
343
344
// NewStream opens a new stream to given peer p, and writes a p2p/protocol
// header with given protocol.ID. If there is no connection to p, attempts
// to create one. If ProtocolID is "", writes no header.
// (Threadsafe)
345
func (h *BasicHost) NewStream(ctx context.Context, p peer.ID, pids ...protocol.ID) (inet.Stream, error) {
346
347
348
349
350
	pref, err := h.preferredProtocol(p, pids)
	if err != nil {
		return nil, err
	}

351
352
353
354
	if pref != "" {
		return h.newStream(ctx, p, pref)
	}

355
	var protoStrs []string
356
	for _, pid := range pids {
357
358
		protoStrs = append(protoStrs, string(pid))
	}
359

360
361
362
363
	s, err := h.Network().NewStream(ctx, p)
	if err != nil {
		return nil, err
	}
364

365
366
367
368
	selected, err := msmux.SelectOneOf(protoStrs, s)
	if err != nil {
		s.Close()
		return nil, err
369
	}
370
371
	selpid := protocol.ID(selected)
	s.SetProtocol(selpid)
372
	h.Peerstore().AddProtocols(p, selected)
373

Jeromy's avatar
Jeromy committed
374
375
376
377
378
	if h.bwc != nil {
		s = mstream.WrapStream(s, h.bwc)
	}

	return s, nil
379
380
}

381
382
383
384
func pidsToStrings(pids []protocol.ID) []string {
	out := make([]string, len(pids))
	for i, p := range pids {
		out[i] = string(p)
385
	}
386
	return out
387
388
}

389
390
391
392
393
func (h *BasicHost) preferredProtocol(p peer.ID, pids []protocol.ID) (protocol.ID, error) {
	pidstrs := pidsToStrings(pids)
	supported, err := h.Peerstore().SupportsProtocols(p, pidstrs...)
	if err != nil {
		return "", err
394
395
	}

396
397
398
399
400
	var out protocol.ID
	if len(supported) > 0 {
		out = protocol.ID(supported[0])
	}
	return out, nil
401
402
403
}

func (h *BasicHost) newStream(ctx context.Context, p peer.ID, pid protocol.ID) (inet.Stream, error) {
404
	s, err := h.Network().NewStream(ctx, p)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
405
406
407
408
	if err != nil {
		return nil, err
	}

409
	s.SetProtocol(pid)
410

Jeromy's avatar
Jeromy committed
411
412
413
	if h.bwc != nil {
		s = mstream.WrapStream(s, h.bwc)
	}
Jeromy's avatar
Jeromy committed
414

Jeromy's avatar
Jeromy committed
415
	lzcon := msmux.NewMSSelect(s, string(pid))
Jeromy's avatar
Jeromy committed
416
	return &streamWrapper{
Jeromy's avatar
Jeromy committed
417
		Stream: s,
Jeromy's avatar
Jeromy committed
418
419
		rw:     lzcon,
	}, nil
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
420
421
422
}

// Connect ensures there is a connection between this host and the peer with
423
424
425
426
// given peer.ID. If there is not an active connection, Connect will issue a
// h.Network.Dial, and block until a connection is open, or an error is returned.
// Connect will absorb the addresses in pi into its internal peerstore.
// It will also resolve any /dns4, /dns6, and /dnsaddr addresses.
Jeromy's avatar
Jeromy committed
427
func (h *BasicHost) Connect(ctx context.Context, pi pstore.PeerInfo) error {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
428
	// absorb addresses into peerstore
Jeromy's avatar
Jeromy committed
429
	h.Peerstore().AddAddrs(pi.ID, pi.Addrs, pstore.TempAddrTTL)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
430
431
432
433
434
435

	cs := h.Network().ConnsToPeer(pi.ID)
	if len(cs) > 0 {
		return nil
	}

436
437
438
439
440
441
	resolved, err := h.resolveAddrs(ctx, h.Peerstore().PeerInfo(pi.ID))
	if err != nil {
		return err
	}
	h.Peerstore().AddAddrs(pi.ID, resolved, pstore.TempAddrTTL)

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
442
443
444
	return h.dialPeer(ctx, pi.ID)
}

445
446
447
448
449
450
451
452
453
454
func (h *BasicHost) resolveAddrs(ctx context.Context, pi pstore.PeerInfo) ([]ma.Multiaddr, error) {
	proto := ma.ProtocolWithCode(ma.P_IPFS).Name
	p2paddr, err := ma.NewMultiaddr("/" + proto + "/" + pi.ID.Pretty())
	if err != nil {
		return nil, err
	}

	var addrs []ma.Multiaddr
	for _, addr := range pi.Addrs {
		addrs = append(addrs, addr)
455
456
457
		if !madns.Matches(addr) {
			continue
		}
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475

		reqaddr := addr.Encapsulate(p2paddr)
		resaddrs, err := h.maResolver.Resolve(ctx, reqaddr)
		if err != nil {
			log.Infof("error resolving %s: %s", reqaddr, err)
		}
		for _, res := range resaddrs {
			pi, err := pstore.InfoFromP2pAddr(res)
			if err != nil {
				log.Infof("error parsing %s: %s", res, err)
			}
			addrs = append(addrs, pi.Addrs...)
		}
	}

	return addrs, nil
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
476
477
478
479
480
481
482
483
484
// dialPeer opens a connection to peer, and makes sure to identify
// the connection once it has been opened.
func (h *BasicHost) dialPeer(ctx context.Context, p peer.ID) error {
	log.Debugf("host %s dialing %s", h.ID, p)
	c, err := h.Network().DialPeer(ctx, p)
	if err != nil {
		return err
	}

485
486
487
488
	// Clear protocols on connecting to new peer to avoid issues caused
	// by misremembering protocols between reconnects
	h.Peerstore().SetProtocols(p)

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
489
490
491
492
493
494
495
496
497
498
499
500
501
502
	// identify the connection before returning.
	done := make(chan struct{})
	go func() {
		h.ids.IdentifyConn(c)
		close(done)
	}()

	// respect don contexteone
	select {
	case <-done:
	case <-ctx.Done():
		return ctx.Err()
	}

Jeromy's avatar
Jeromy committed
503
	log.Debugf("host %s finished dialing %s", h.ID(), p)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
504
505
506
	return nil
}

Jeromy's avatar
Jeromy committed
507
508
509
510
func (h *BasicHost) ConnManager() connmgr.ConnManager {
	return h.cmgr
}

511
512
// Addrs returns listening addresses that are safe to announce to the network.
// The output is the same as AllAddrs, but processed by AddrsFactory.
513
func (h *BasicHost) Addrs() []ma.Multiaddr {
514
515
516
517
518
519
	return h.addrs(h.AllAddrs())
}

// AllAddrs returns all the addresses of BasicHost at this moment in time.
// It's ok to not include addresses if they're not available to be used now.
func (h *BasicHost) AllAddrs() []ma.Multiaddr {
520
521
522
523
524
	addrs, err := h.Network().InterfaceListenAddresses()
	if err != nil {
		log.Debug("error retrieving network interface addrs")
	}

525
526
527
528
	if h.ids != nil { // add external observed addresses
		addrs = append(addrs, h.ids.OwnObservedAddrs()...)
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
529
530
531
532
533
	if h.natmgr != nil { // natmgr is nil if we do not use nat option.
		nat := h.natmgr.NAT()
		if nat != nil { // nat is nil if not ready, or no nat is available.
			addrs = append(addrs, nat.ExternalAddrs()...)
		}
534
535
536
537
538
	}

	return addrs
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
539
540
// Close shuts down the Host's services (network, etc).
func (h *BasicHost) Close() error {
541
	return h.proc.Close()
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
542
}
Jeromy's avatar
Jeromy committed
543

544
// GetBandwidthReporter exposes the Host's bandiwth metrics reporter
Jeromy's avatar
Jeromy committed
545
546
547
func (h *BasicHost) GetBandwidthReporter() metrics.Reporter {
	return h.bwc
}
Jeromy's avatar
Jeromy committed
548
549
550
551
552
553
554
555
556
557
558
559
560

type streamWrapper struct {
	inet.Stream
	rw io.ReadWriter
}

func (s *streamWrapper) Read(b []byte) (int, error) {
	return s.rw.Read(b)
}

func (s *streamWrapper) Write(b []byte) (int, error) {
	return s.rw.Write(b)
}