basic_host.go 9.99 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"
Jeromy's avatar
Jeromy committed
12
13
14
	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
15
16
	peer "github.com/libp2p/go-libp2p-peer"
	pstore "github.com/libp2p/go-libp2p-peerstore"
Jeromy's avatar
Jeromy committed
17
	protocol "github.com/libp2p/go-libp2p-protocol"
Jeromy's avatar
Jeromy committed
18
	ma "github.com/multiformats/go-multiaddr"
Jeromy's avatar
Jeromy committed
19
	msmux "github.com/multiformats/go-multistream"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
20
21
)

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

24
25
var NegotiateTimeout = time.Second * 60

26
27
28
29
// 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
30
// Option is a type used to pass in options to the host.
31
32
33
type Option int

const (
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
34
35
36
37
38
	// 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.
39
40
41
	NATPortMap Option = iota
)

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
42
43
44
45
46
// 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
47
48
type BasicHost struct {
	network inet.Network
Jeromy's avatar
Jeromy committed
49
	mux     *msmux.MultistreamMuxer
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
50
	ids     *identify.IDService
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
51
	natmgr  *natManager
52
	addrs   AddrsFactory
53

54
55
	NegotiateTimeout time.Duration

56
	proc goprocess.Process
Jeromy's avatar
Jeromy committed
57
58

	bwc metrics.Reporter
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
59
60
61
}

// New constructs and sets up a new *BasicHost with given Network
Jeromy's avatar
Jeromy committed
62
func New(net inet.Network, opts ...interface{}) *BasicHost {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
63
	h := &BasicHost{
64
65
66
		network:          net,
		mux:              msmux.NewMultistreamMuxer(),
		NegotiateTimeout: NegotiateTimeout,
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
67
68
	}

69
	h.proc = goprocess.WithTeardown(func() error {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
70
71
72
73
		if h.natmgr != nil {
			h.natmgr.Close()
		}

74
75
76
		return h.Network().Close()
	})

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
77
78
	// setup host services
	h.ids = identify.NewIDService(h)
Jeromy's avatar
Jeromy committed
79

80
81
82
	// default addresses factory, can be overridden via opts argument
	h.addrs = func(addrs []ma.Multiaddr) []ma.Multiaddr { return addrs }

83
	for _, o := range opts {
Jeromy's avatar
Jeromy committed
84
85
86
87
88
89
90
91
		switch o := o.(type) {
		case Option:
			switch o {
			case NATPortMap:
				h.natmgr = newNatManager(h)
			}
		case metrics.Reporter:
			h.bwc = o
92
93
		case AddrsFactory:
			h.addrs = AddrsFactory(o)
94
95
96
		}
	}

Jeromy's avatar
Jeromy committed
97
98
	h.ids.Reporter = h.bwc

Jeromy's avatar
Jeromy committed
99
100
101
	net.SetConnHandler(h.newConnHandler)
	net.SetStreamHandler(h.newStreamHandler)

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
102
103
104
105
106
	return h
}

// newConnHandler is the remote-opened conn handler for inet.Network
func (h *BasicHost) newConnHandler(c inet.Conn) {
107
108
109
	// 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
110
111
112
113
	h.ids.IdentifyConn(c)
}

// newStreamHandler is the remote-opened stream handler for inet.Network
Jeromy's avatar
Jeromy committed
114
// TODO: this feels a bit wonky
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
115
func (h *BasicHost) newStreamHandler(s inet.Stream) {
116
	before := time.Now()
117
118
119
120
121
122
123
124
125

	if h.NegotiateTimeout != 0 {
		if err := s.SetDeadline(time.Now().Add(h.NegotiateTimeout)); err != nil {
			log.Error("setting stream deadline: ", err)
			s.Close()
			return
		}
	}

126
	lzc, protoID, handle, err := h.Mux().NegotiateLazy(s)
127
	took := time.Now().Sub(before)
Jeromy's avatar
Jeromy committed
128
	if err != nil {
129
		if err == io.EOF {
130
131
132
133
134
			logf := log.Debugf
			if took > time.Second*10 {
				logf = log.Warningf
			}
			logf("protocol EOF: %s (took %s)", s.Conn().RemotePeer(), took)
135
		} else {
136
			log.Warning("protocol mux failed: %s (took %s)", err, took)
137
		}
138
		s.Close()
Jeromy's avatar
Jeromy committed
139
140
		return
	}
141

142
143
144
145
146
	s = &streamWrapper{
		Stream: s,
		rw:     lzc,
	}

147
148
149
150
151
152
153
154
	if h.NegotiateTimeout != 0 {
		if err := s.SetDeadline(time.Time{}); err != nil {
			log.Error("resetting stream deadline: ", err)
			s.Close()
			return
		}
	}

155
	s.SetProtocol(protocol.ID(protoID))
Jeromy's avatar
Jeromy committed
156

157
158
159
160
	if h.bwc != nil {
		s = mstream.WrapStream(s, h.bwc)
	}
	log.Debugf("protocol negotiation took %s", took)
Jeromy's avatar
Jeromy committed
161

162
	go handle(protoID, s)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
163
164
165
166
167
168
169
170
}

// 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
171
func (h *BasicHost) Peerstore() pstore.Peerstore {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
172
173
174
	return h.Network().Peerstore()
}

175
// Network returns the Network interface of the Host
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
176
177
178
179
180
func (h *BasicHost) Network() inet.Network {
	return h.network
}

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

185
// IDService returns
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
186
187
188
189
190
191
192
193
194
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) {
195
196
	h.Mux().AddHandler(string(pid), func(p string, rwc io.ReadWriteCloser) error {
		is := rwc.(inet.Stream)
197
		is.SetProtocol(protocol.ID(p))
198
		handler(is)
Jeromy's avatar
Jeromy committed
199
200
		return nil
	})
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
201
202
}

203
204
205
206
207
// 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)
208
		is.SetProtocol(protocol.ID(p))
209
210
211
212
213
		handler(is)
		return nil
	})
}

214
// RemoveStreamHandler returns ..
Jeromy's avatar
Jeromy committed
215
func (h *BasicHost) RemoveStreamHandler(pid protocol.ID) {
Jeromy's avatar
Jeromy committed
216
	h.Mux().RemoveHandler(string(pid))
Jeromy's avatar
Jeromy committed
217
218
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
219
220
221
222
// 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)
223
func (h *BasicHost) NewStream(ctx context.Context, p peer.ID, pids ...protocol.ID) (inet.Stream, error) {
224
225
226
227
228
	pref, err := h.preferredProtocol(p, pids)
	if err != nil {
		return nil, err
	}

229
230
231
232
	if pref != "" {
		return h.newStream(ctx, p, pref)
	}

233
	var protoStrs []string
234
	for _, pid := range pids {
235
236
		protoStrs = append(protoStrs, string(pid))
	}
237

238
239
240
241
	s, err := h.Network().NewStream(ctx, p)
	if err != nil {
		return nil, err
	}
242

243
244
245
246
	selected, err := msmux.SelectOneOf(protoStrs, s)
	if err != nil {
		s.Close()
		return nil, err
247
	}
248
249
	selpid := protocol.ID(selected)
	s.SetProtocol(selpid)
250
	h.Peerstore().AddProtocols(p, selected)
251

Jeromy's avatar
Jeromy committed
252
253
254
255
256
	if h.bwc != nil {
		s = mstream.WrapStream(s, h.bwc)
	}

	return s, nil
257
258
}

259
260
261
262
func pidsToStrings(pids []protocol.ID) []string {
	out := make([]string, len(pids))
	for i, p := range pids {
		out[i] = string(p)
263
	}
264
	return out
265
266
}

267
268
269
270
271
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
272
273
	}

274
275
276
277
278
	var out protocol.ID
	if len(supported) > 0 {
		out = protocol.ID(supported[0])
	}
	return out, nil
279
280
281
}

func (h *BasicHost) newStream(ctx context.Context, p peer.ID, pid protocol.ID) (inet.Stream, error) {
282
	s, err := h.Network().NewStream(ctx, p)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
283
284
285
286
	if err != nil {
		return nil, err
	}

287
	s.SetProtocol(pid)
288

Jeromy's avatar
Jeromy committed
289
290
291
	if h.bwc != nil {
		s = mstream.WrapStream(s, h.bwc)
	}
Jeromy's avatar
Jeromy committed
292

Jeromy's avatar
Jeromy committed
293
	lzcon := msmux.NewMSSelect(s, string(pid))
Jeromy's avatar
Jeromy committed
294
	return &streamWrapper{
Jeromy's avatar
Jeromy committed
295
		Stream: s,
Jeromy's avatar
Jeromy committed
296
297
		rw:     lzcon,
	}, nil
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
298
299
300
301
302
303
}

// Connect ensures there is a connection between this host and the peer with
// given peer.ID. Connect will absorb the addresses in pi into its internal
// peerstore. 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
304
// returned.
Jeromy's avatar
Jeromy committed
305
func (h *BasicHost) Connect(ctx context.Context, pi pstore.PeerInfo) error {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
306
307

	// absorb addresses into peerstore
Jeromy's avatar
Jeromy committed
308
	h.Peerstore().AddAddrs(pi.ID, pi.Addrs, pstore.TempAddrTTL)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326

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

	return h.dialPeer(ctx, pi.ID)
}

// 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
	}

327
328
329
330
	// 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
331
332
333
334
335
336
337
338
339
340
341
342
343
344
	// 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
345
	log.Debugf("host %s finished dialing %s", h.ID(), p)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
346
347
348
	return nil
}

349
350
// Addrs returns listening addresses that are safe to announce to the network.
// The output is the same as AllAddrs, but processed by AddrsFactory.
351
func (h *BasicHost) Addrs() []ma.Multiaddr {
352
353
354
355
356
357
	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 {
358
359
360
361
362
	addrs, err := h.Network().InterfaceListenAddresses()
	if err != nil {
		log.Debug("error retrieving network interface addrs")
	}

363
364
365
366
	if h.ids != nil { // add external observed addresses
		addrs = append(addrs, h.ids.OwnObservedAddrs()...)
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
367
368
369
370
371
	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()...)
		}
372
373
374
375
376
	}

	return addrs
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
377
378
// Close shuts down the Host's services (network, etc).
func (h *BasicHost) Close() error {
379
	return h.proc.Close()
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
380
}
Jeromy's avatar
Jeromy committed
381

382
// GetBandwidthReporter exposes the Host's bandiwth metrics reporter
Jeromy's avatar
Jeromy committed
383
384
385
func (h *BasicHost) GetBandwidthReporter() metrics.Reporter {
	return h.bwc
}
Jeromy's avatar
Jeromy committed
386
387
388
389
390
391
392
393
394
395
396
397
398

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)
}