notif.go 3.83 KB
Newer Older
1 2 3
package dht

import (
4 5 6
	"github.com/libp2p/go-libp2p-core/helpers"
	"github.com/libp2p/go-libp2p-core/network"

7
	ma "github.com/multiformats/go-multiaddr"
Jeromy's avatar
Jeromy committed
8
	mstream "github.com/multiformats/go-multistream"
9 10 11 12 13 14 15 16 17
)

// netNotifiee defines methods to be used with the IpfsDHT
type netNotifiee IpfsDHT

func (nn *netNotifiee) DHT() *IpfsDHT {
	return (*IpfsDHT)(nn)
}

18
func (nn *netNotifiee) Connected(n network.Network, v network.Conn) {
19 20
	dht := nn.DHT()
	select {
21
	case <-dht.Process().Closing():
22
		return
23
	default:
24
	}
25

Steven Allen's avatar
Steven Allen committed
26
	p := v.RemotePeer()
27
	protos, err := dht.peerstore.SupportsProtocols(p, dht.protocolStrs()...)
Steven Allen's avatar
Steven Allen committed
28
	if err == nil && len(protos) != 0 {
29 30 31
		// We lock here for consistency with the lock in testConnection.
		// This probably isn't necessary because (dis)connect
		// notifications are serialized but it's nice to be consistent.
Steven Allen's avatar
Steven Allen committed
32 33
		dht.plk.Lock()
		defer dht.plk.Unlock()
34
		if dht.host.Network().Connectedness(p) == network.Connected {
35
			refresh := dht.routingTable.Size() <= minRTRefreshThreshold
Steven Allen's avatar
Steven Allen committed
36
			dht.Update(dht.Context(), p)
37
			if refresh && dht.autoRefresh {
Aarsh Shah's avatar
Aarsh Shah committed
38
				select {
Steven Allen's avatar
Steven Allen committed
39
				case dht.triggerRtRefresh <- nil:
Aarsh Shah's avatar
Aarsh Shah committed
40 41 42
				default:
				}
			}
Steven Allen's avatar
Steven Allen committed
43
		}
44 45 46
		return
	}

47
	// Note: Unfortunately, the peerstore may not yet know that this peer is
Steven Allen's avatar
Steven Allen committed
48 49 50
	// a DHT server. So, if it didn't return a positive response above, test
	// manually.
	go nn.testConnection(v)
Steven Allen's avatar
Steven Allen committed
51 52
}

53
func (nn *netNotifiee) testConnection(v network.Conn) {
Steven Allen's avatar
Steven Allen committed
54
	dht := nn.DHT()
Steven Allen's avatar
Steven Allen committed
55 56 57 58 59 60 61 62
	p := v.RemotePeer()

	// Forcibly use *this* connection. Otherwise, if we have two connections, we could:
	// 1. Test it twice.
	// 2. Have it closed from under us leaving the second (open) connection untested.
	s, err := v.NewStream()
	if err != nil {
		// Connection error
Steven Allen's avatar
Steven Allen committed
63 64
		return
	}
65
	defer helpers.FullClose(s)
Steven Allen's avatar
Steven Allen committed
66

67
	selected, err := mstream.SelectOneOf(dht.protocolStrs(), s)
Steven Allen's avatar
Steven Allen committed
68 69 70 71 72 73 74
	if err != nil {
		// Doesn't support the protocol
		return
	}
	// Remember this choice (makes subsequent negotiations faster)
	dht.peerstore.AddProtocols(p, selected)

75 76 77
	// We lock here as we race with disconnect. If we didn't lock, we could
	// finish processing a connect after handling the associated disconnect
	// event and add the peer to the routing table after removing it.
Steven Allen's avatar
Steven Allen committed
78 79
	dht.plk.Lock()
	defer dht.plk.Unlock()
80
	if dht.host.Network().Connectedness(p) == network.Connected {
81
		refresh := dht.routingTable.Size() <= minRTRefreshThreshold
Steven Allen's avatar
Steven Allen committed
82
		dht.Update(dht.Context(), p)
83
		if refresh && dht.autoRefresh {
Aarsh Shah's avatar
Aarsh Shah committed
84
			select {
Steven Allen's avatar
Steven Allen committed
85
			case dht.triggerRtRefresh <- nil:
Aarsh Shah's avatar
Aarsh Shah committed
86 87 88
			default:
			}
		}
Steven Allen's avatar
Steven Allen committed
89
	}
90 91
}

92
func (nn *netNotifiee) Disconnected(n network.Network, v network.Conn) {
93 94
	dht := nn.DHT()
	select {
95
	case <-dht.Process().Closing():
96
		return
97
	default:
98
	}
99

100
	p := v.RemotePeer()
101

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

111
	dht.routingTable.Remove(p)
112 113 114 115 116 117 118 119 120 121
	if dht.routingTable.Size() < minRTRefreshThreshold {
		// TODO: Actively bootstrap. For now, just try to add the currently connected peers.
		for _, p := range dht.host.Network().Peers() {
			// Don't bother probing, we do that on connect.
			protos, err := dht.peerstore.SupportsProtocols(p, dht.protocolStrs()...)
			if err == nil && len(protos) != 0 {
				dht.Update(dht.Context(), p)
			}
		}
	}
122 123 124 125

	dht.smlk.Lock()
	defer dht.smlk.Unlock()
	ms, ok := dht.strmap[p]
126 127 128
	if !ok {
		return
	}
129 130 131 132 133 134 135 136
	delete(dht.strmap, p)

	// Do this asynchronously as ms.lk can block for a while.
	go func() {
		ms.lk.Lock()
		defer ms.lk.Unlock()
		ms.invalidate()
	}()
137 138
}

139 140 141 142
func (nn *netNotifiee) OpenedStream(n network.Network, v network.Stream) {}
func (nn *netNotifiee) ClosedStream(n network.Network, v network.Stream) {}
func (nn *netNotifiee) Listen(n network.Network, a ma.Multiaddr)         {}
func (nn *netNotifiee) ListenClose(n network.Network, a ma.Multiaddr)    {}