basic_host.go 9.4 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

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
26
// Option is a type used to pass in options to the host.
27
28
29
type Option int

const (
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
30
31
32
33
34
	// 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.
35
36
37
	NATPortMap Option = iota
)

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
38
39
40
41
42
// 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
43
44
type BasicHost struct {
	network inet.Network
Jeromy's avatar
Jeromy committed
45
	mux     *msmux.MultistreamMuxer
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
46
	ids     *identify.IDService
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
47
	natmgr  *natManager
48

49
50
	NegotiateTimeout time.Duration

51
	proc goprocess.Process
Jeromy's avatar
Jeromy committed
52
53

	bwc metrics.Reporter
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
54
55
56
}

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

64
	h.proc = goprocess.WithTeardown(func() error {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
65
66
67
68
		if h.natmgr != nil {
			h.natmgr.Close()
		}

69
70
71
		return h.Network().Close()
	})

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
72
73
	// setup host services
	h.ids = identify.NewIDService(h)
Jeromy's avatar
Jeromy committed
74

75
	for _, o := range opts {
Jeromy's avatar
Jeromy committed
76
77
78
79
80
81
82
83
		switch o := o.(type) {
		case Option:
			switch o {
			case NATPortMap:
				h.natmgr = newNatManager(h)
			}
		case metrics.Reporter:
			h.bwc = o
84
85
86
		}
	}

Jeromy's avatar
Jeromy committed
87
88
	h.ids.Reporter = h.bwc

Jeromy's avatar
Jeromy committed
89
90
91
	net.SetConnHandler(h.newConnHandler)
	net.SetStreamHandler(h.newStreamHandler)

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
92
93
94
95
96
	return h
}

// newConnHandler is the remote-opened conn handler for inet.Network
func (h *BasicHost) newConnHandler(c inet.Conn) {
97
98
99
	// 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
100
101
102
103
	h.ids.IdentifyConn(c)
}

// newStreamHandler is the remote-opened stream handler for inet.Network
Jeromy's avatar
Jeromy committed
104
// TODO: this feels a bit wonky
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
105
func (h *BasicHost) newStreamHandler(s inet.Stream) {
106
	before := time.Now()
107
108
109
110
111
112
113
114
115

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

116
	lzc, protoID, handle, err := h.Mux().NegotiateLazy(s)
117
	took := time.Now().Sub(before)
Jeromy's avatar
Jeromy committed
118
	if err != nil {
119
		if err == io.EOF {
120
121
122
123
124
			logf := log.Debugf
			if took > time.Second*10 {
				logf = log.Warningf
			}
			logf("protocol EOF: %s (took %s)", s.Conn().RemotePeer(), took)
125
		} else {
126
			log.Warning("protocol mux failed: %s (took %s)", err, took)
127
		}
128
		s.Close()
Jeromy's avatar
Jeromy committed
129
130
		return
	}
131

132
133
134
135
136
	s = &streamWrapper{
		Stream: s,
		rw:     lzc,
	}

137
138
139
140
141
142
143
144
	if h.NegotiateTimeout != 0 {
		if err := s.SetDeadline(time.Time{}); err != nil {
			log.Error("resetting stream deadline: ", err)
			s.Close()
			return
		}
	}

145
	s.SetProtocol(protocol.ID(protoID))
Jeromy's avatar
Jeromy committed
146

147
148
149
150
	if h.bwc != nil {
		s = mstream.WrapStream(s, h.bwc)
	}
	log.Debugf("protocol negotiation took %s", took)
Jeromy's avatar
Jeromy committed
151

152
	go handle(protoID, s)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
153
154
155
156
157
158
159
160
}

// 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
161
func (h *BasicHost) Peerstore() pstore.Peerstore {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
162
163
164
	return h.Network().Peerstore()
}

165
// Network returns the Network interface of the Host
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
166
167
168
169
170
func (h *BasicHost) Network() inet.Network {
	return h.network
}

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

175
// IDService returns
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
176
177
178
179
180
181
182
183
184
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) {
185
186
	h.Mux().AddHandler(string(pid), func(p string, rwc io.ReadWriteCloser) error {
		is := rwc.(inet.Stream)
187
		is.SetProtocol(protocol.ID(p))
188
		handler(is)
Jeromy's avatar
Jeromy committed
189
190
		return nil
	})
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
191
192
}

193
194
195
196
197
// 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)
198
		is.SetProtocol(protocol.ID(p))
199
200
201
202
203
		handler(is)
		return nil
	})
}

204
// RemoveStreamHandler returns ..
Jeromy's avatar
Jeromy committed
205
func (h *BasicHost) RemoveStreamHandler(pid protocol.ID) {
Jeromy's avatar
Jeromy committed
206
	h.Mux().RemoveHandler(string(pid))
Jeromy's avatar
Jeromy committed
207
208
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
209
210
211
212
// 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)
213
func (h *BasicHost) NewStream(ctx context.Context, p peer.ID, pids ...protocol.ID) (inet.Stream, error) {
214
215
216
217
218
	pref, err := h.preferredProtocol(p, pids)
	if err != nil {
		return nil, err
	}

219
220
221
222
	if pref != "" {
		return h.newStream(ctx, p, pref)
	}

223
	var protoStrs []string
224
	for _, pid := range pids {
225
226
		protoStrs = append(protoStrs, string(pid))
	}
227

228
229
230
231
	s, err := h.Network().NewStream(ctx, p)
	if err != nil {
		return nil, err
	}
232

233
234
235
236
	selected, err := msmux.SelectOneOf(protoStrs, s)
	if err != nil {
		s.Close()
		return nil, err
237
	}
238
239
	selpid := protocol.ID(selected)
	s.SetProtocol(selpid)
240
	h.Peerstore().AddProtocols(p, selected)
241

Jeromy's avatar
Jeromy committed
242
243
244
245
246
	if h.bwc != nil {
		s = mstream.WrapStream(s, h.bwc)
	}

	return s, nil
247
248
}

249
250
251
252
func pidsToStrings(pids []protocol.ID) []string {
	out := make([]string, len(pids))
	for i, p := range pids {
		out[i] = string(p)
253
	}
254
	return out
255
256
}

257
258
259
260
261
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
262
263
	}

264
265
266
267
268
	var out protocol.ID
	if len(supported) > 0 {
		out = protocol.ID(supported[0])
	}
	return out, nil
269
270
271
}

func (h *BasicHost) newStream(ctx context.Context, p peer.ID, pid protocol.ID) (inet.Stream, error) {
272
	s, err := h.Network().NewStream(ctx, p)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
273
274
275
276
	if err != nil {
		return nil, err
	}

277
	s.SetProtocol(pid)
278

Jeromy's avatar
Jeromy committed
279
280
281
	if h.bwc != nil {
		s = mstream.WrapStream(s, h.bwc)
	}
Jeromy's avatar
Jeromy committed
282

Jeromy's avatar
Jeromy committed
283
	lzcon := msmux.NewMSSelect(s, string(pid))
Jeromy's avatar
Jeromy committed
284
	return &streamWrapper{
Jeromy's avatar
Jeromy committed
285
		Stream: s,
Jeromy's avatar
Jeromy committed
286
287
		rw:     lzcon,
	}, nil
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
288
289
290
291
292
293
}

// 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
294
// returned.
Jeromy's avatar
Jeromy committed
295
func (h *BasicHost) Connect(ctx context.Context, pi pstore.PeerInfo) error {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
296
297

	// absorb addresses into peerstore
Jeromy's avatar
Jeromy committed
298
	h.Peerstore().AddAddrs(pi.ID, pi.Addrs, pstore.TempAddrTTL)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316

	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
	}

317
318
319
320
	// 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
321
322
323
324
325
326
327
328
329
330
331
332
333
334
	// 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
335
	log.Debugf("host %s finished dialing %s", h.ID(), p)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
336
337
338
	return nil
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
339
340
// Addrs 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.
341
342
343
344
345
346
func (h *BasicHost) Addrs() []ma.Multiaddr {
	addrs, err := h.Network().InterfaceListenAddresses()
	if err != nil {
		log.Debug("error retrieving network interface addrs")
	}

347
348
349
350
	if h.ids != nil { // add external observed addresses
		addrs = append(addrs, h.ids.OwnObservedAddrs()...)
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
351
352
353
354
355
	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()...)
		}
356
357
358
359
360
	}

	return addrs
}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
361
362
// Close shuts down the Host's services (network, etc).
func (h *BasicHost) Close() error {
363
	return h.proc.Close()
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
364
}
Jeromy's avatar
Jeromy committed
365

366
// GetBandwidthReporter exposes the Host's bandiwth metrics reporter
Jeromy's avatar
Jeromy committed
367
368
369
func (h *BasicHost) GetBandwidthReporter() metrics.Reporter {
	return h.bwc
}
Jeromy's avatar
Jeromy committed
370
371
372
373
374
375
376
377
378
379
380
381
382

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