subscriber_notifee.go 6.85 KB
Newer Older
1 2 3
package dht

import (
4 5
	"fmt"

6 7
	"github.com/libp2p/go-libp2p-core/event"
	"github.com/libp2p/go-libp2p-core/network"
Adin Schmahmann's avatar
Adin Schmahmann committed
8
	"github.com/libp2p/go-libp2p-core/peer"
9
	"github.com/libp2p/go-libp2p-core/protocol"
10 11 12 13 14 15 16 17 18 19 20

	"github.com/libp2p/go-eventbus"

	ma "github.com/multiformats/go-multiaddr"

	"github.com/jbenet/goprocess"
)

// subscriberNotifee implements network.Notifee and also manages the subscriber to the event bus. We consume peer
// identification events to trigger inclusion in the routing table, and we consume Disconnected events to eject peers
// from it.
21 22 23
type subscriberNotifee struct {
	dht  *IpfsDHT
	subs event.Subscription
24 25
}

26 27
func newSubscriberNotifiee(dht *IpfsDHT) (*subscriberNotifee, error) {
	bufSize := eventbus.BufSize(256)
28 29

	evts := []interface{}{
30 31 32 33 34 35
		// register for event bus notifications of when peers successfully complete identification in order to update
		// the routing table
		new(event.EvtPeerIdentificationCompleted),

		// register for event bus protocol ID changes in order to update the routing table
		new(event.EvtPeerProtocolsUpdated),
Aarsh Shah's avatar
Aarsh Shah committed
36 37 38 39

		// register for event bus notifications for when our local address/addresses change so we can
		// advertise those to the network
		new(event.EvtLocalAddressesUpdated),
40 41 42 43
	}

	// register for event bus local routability changes in order to trigger switching between client and server modes
	// only register for events if the DHT is operating in ModeAuto
44
	if dht.auto == ModeAuto || dht.auto == ModeAutoServer {
45
		evts = append(evts, new(event.EvtLocalReachabilityChanged))
46 47
	}

48
	subs, err := dht.host.EventBus().Subscribe(evts, bufSize)
49
	if err != nil {
50
		return nil, fmt.Errorf("dht could not subscribe to eventbus events; err: %s", err)
51 52
	}

53 54 55 56 57 58 59 60 61
	nn := &subscriberNotifee{
		dht:  dht,
		subs: subs,
	}

	// register for network notifications
	dht.host.Network().Notify(nn)

	// Fill routing table with currently connected peers that are DHT servers
62
	dht.plk.Lock()
63
	defer dht.plk.Unlock()
64
	for _, p := range dht.host.Network().Peers() {
Aarsh Shah's avatar
Aarsh Shah committed
65
		dht.peerFound(dht.ctx, p, false)
66
	}
67 68 69 70 71 72 73 74

	return nn, nil
}

func (nn *subscriberNotifee) subscribe(proc goprocess.Process) {
	dht := nn.dht
	defer dht.host.Network().StopNotify(nn)
	defer nn.subs.Close()
75 76 77

	for {
		select {
78
		case e, more := <-nn.subs.Out():
79 80 81 82
			if !more {
				return
			}

83
			switch evt := e.(type) {
Aarsh Shah's avatar
Aarsh Shah committed
84 85 86 87 88 89 90 91 92 93
			case event.EvtLocalAddressesUpdated:
				// when our address changes, we should proactively tell our closest peers about it so
				// we become discoverable quickly. The Identify protocol will push a signed peer record
				// with our new address to all peers we are connected to. However, we might not necessarily be connected
				// to our closet peers & so in the true spirit of Zen, searching for ourself in the network really is the best way
				// to to forge connections with those matter.
				select {
				case dht.triggerSelfLookup <- nil:
				default:
				}
94 95
			case event.EvtPeerProtocolsUpdated:
				handlePeerProtocolsUpdatedEvent(dht, evt)
Aarsh Shah's avatar
Aarsh Shah committed
96 97
			case event.EvtPeerIdentificationCompleted:
				handlePeerIdentificationCompletedEvent(dht, evt)
98
			case event.EvtLocalReachabilityChanged:
99
				if dht.auto == ModeAuto || dht.auto == ModeAutoServer {
100 101 102 103
					handleLocalReachabilityChangedEvent(dht, evt)
				} else {
					// something has gone really wrong if we get an event we did not subscribe to
					logger.Errorf("received LocalReachabilityChanged event that was not subscribed to")
104
				}
105 106 107
			default:
				// something has gone really wrong if we get an event for another type
				logger.Errorf("got wrong type from subscription: %T", e)
108 109 110 111 112 113 114
			}
		case <-proc.Closing():
			return
		}
	}
}

115 116 117 118 119 120 121 122
func handlePeerIdentificationCompletedEvent(dht *IpfsDHT, e event.EvtPeerIdentificationCompleted) {
	dht.plk.Lock()
	defer dht.plk.Unlock()
	if dht.host.Network().Connectedness(e.Peer) != network.Connected {
		return
	}

	// if the peer supports the DHT protocol, add it to our RT and kick a refresh if needed
Adin Schmahmann's avatar
Adin Schmahmann committed
123
	valid, err := dht.validRTPeer(e.Peer)
124 125 126
	if err != nil {
		logger.Errorf("could not check peerstore for protocol support: err: %s", err)
		return
Aarsh Shah's avatar
Aarsh Shah committed
127 128
	} else if valid {
		dht.peerFound(dht.ctx, e.Peer, false)
129
		dht.fixRTIfNeeded()
130 131 132 133
	}
}

func handlePeerProtocolsUpdatedEvent(dht *IpfsDHT, e event.EvtPeerProtocolsUpdated) {
Adin Schmahmann's avatar
Adin Schmahmann committed
134
	valid, err := dht.validRTPeer(e.Peer)
135 136 137 138 139
	if err != nil {
		logger.Errorf("could not check peerstore for protocol support: err: %s", err)
		return
	}

Aarsh Shah's avatar
Aarsh Shah committed
140
	if !valid {
Aarsh Shah's avatar
Aarsh Shah committed
141
		dht.peerStoppedDHT(dht.ctx, e.Peer)
Aarsh Shah's avatar
Aarsh Shah committed
142
		return
143 144
	}

Aarsh Shah's avatar
Aarsh Shah committed
145
	// we just might have discovered a peer that supports the DHT protocol
146
	dht.fixRTIfNeeded()
147 148 149 150 151 152
}

func handleLocalReachabilityChangedEvent(dht *IpfsDHT, e event.EvtLocalReachabilityChanged) {
	var target mode

	switch e.Reachability {
153
	case network.ReachabilityPrivate:
154
		target = modeClient
155 156 157 158 159 160
	case network.ReachabilityUnknown:
		if dht.auto == ModeAutoServer {
			target = modeServer
		} else {
			target = modeClient
		}
161 162 163 164 165 166 167 168 169
	case network.ReachabilityPublic:
		target = modeServer
	}

	logger.Infof("processed event %T; performing dht mode switch", e)

	err := dht.setMode(target)
	// NOTE: the mode will be printed out as a decimal.
	if err == nil {
Steven Allen's avatar
Steven Allen committed
170
		logger.Infow("switched DHT mode successfully", "mode", target)
171
	} else {
Steven Allen's avatar
Steven Allen committed
172
		logger.Errorw("switching DHT mode failed", "mode", target, "error", err)
173 174 175
	}
}

Adin Schmahmann's avatar
Adin Schmahmann committed
176 177 178 179
// validRTPeer returns true if the peer supports the DHT protocol and false otherwise. Supporting the DHT protocol means
// supporting the primary protocols, we do not want to add peers that are speaking obsolete secondary protocols to our
// routing table
func (dht *IpfsDHT) validRTPeer(p peer.ID) (bool, error) {
180
	protos, err := dht.peerstore.SupportsProtocols(p, protocol.ConvertToStrings(dht.protocols)...)
181
	if len(protos) == 0 || err != nil {
Adin Schmahmann's avatar
Adin Schmahmann committed
182 183 184
		return false, err
	}

185
	return dht.routingTablePeerFilter == nil || dht.routingTablePeerFilter(dht, dht.Host().Network().ConnsToPeer(p)), nil
Adin Schmahmann's avatar
Adin Schmahmann committed
186 187
}

188
func (nn *subscriberNotifee) Disconnected(n network.Network, v network.Conn) {
189
	dht := nn.dht
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229
	select {
	case <-dht.Process().Closing():
		return
	default:
	}

	p := v.RemotePeer()

	// Lock and check to see if we're still connected. We lock to make sure
	// we don't concurrently process a connect event.
	dht.plk.Lock()
	defer dht.plk.Unlock()
	if dht.host.Network().Connectedness(p) == network.Connected {
		// We're still connected.
		return
	}

	dht.smlk.Lock()
	defer dht.smlk.Unlock()
	ms, ok := dht.strmap[p]
	if !ok {
		return
	}
	delete(dht.strmap, p)

	// Do this asynchronously as ms.lk can block for a while.
	go func() {
		if err := ms.lk.Lock(dht.Context()); err != nil {
			return
		}
		defer ms.lk.Unlock()
		ms.invalidate()
	}()
}

func (nn *subscriberNotifee) Connected(n network.Network, v network.Conn)      {}
func (nn *subscriberNotifee) OpenedStream(n network.Network, v network.Stream) {}
func (nn *subscriberNotifee) ClosedStream(n network.Network, v network.Stream) {}
func (nn *subscriberNotifee) Listen(n network.Network, a ma.Multiaddr)         {}
func (nn *subscriberNotifee) ListenClose(n network.Network, a ma.Multiaddr)    {}