forked from libp2p/go-libp2p-kad-dht
-
Notifications
You must be signed in to change notification settings - Fork 0
/
dht_filters.go
240 lines (196 loc) · 5.71 KB
/
dht_filters.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
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
230
231
232
233
234
235
236
237
238
239
240
package dht
import (
"bytes"
"net"
"sync"
"time"
"github.com/libp2p/go-libp2p/core/host"
"github.com/libp2p/go-libp2p/core/network"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/google/gopacket/routing"
netroute "github.com/libp2p/go-netroute"
ma "github.com/multiformats/go-multiaddr"
manet "github.com/multiformats/go-multiaddr/net"
dhtcfg "github.com/libp2p/go-libp2p-kad-dht/internal/config"
)
// QueryFilterFunc is a filter applied when considering peers to dial when querying
type QueryFilterFunc = dhtcfg.QueryFilterFunc
// RouteTableFilterFunc is a filter applied when considering connections to keep in
// the local route table.
type RouteTableFilterFunc = dhtcfg.RouteTableFilterFunc
var publicCIDR6 = "2000::/3"
var public6 *net.IPNet
func init() {
_, public6, _ = net.ParseCIDR(publicCIDR6)
}
// isPublicAddr follows the logic of manet.IsPublicAddr, except it uses
// a stricter definition of "public" for ipv6: namely "is it in 2000::/3"?
func isPublicAddr(a ma.Multiaddr) bool {
ip, err := manet.ToIP(a)
if err != nil {
return false
}
if ip.To4() != nil {
return !inAddrRange(ip, manet.Private4) && !inAddrRange(ip, manet.Unroutable4)
}
return public6.Contains(ip)
}
// isPrivateAddr follows the logic of manet.IsPrivateAddr, except that
// it uses a stricter definition of "public" for ipv6
func isPrivateAddr(a ma.Multiaddr) bool {
ip, err := manet.ToIP(a)
if err != nil {
return false
}
if ip.To4() != nil {
return inAddrRange(ip, manet.Private4)
}
return !public6.Contains(ip) && !inAddrRange(ip, manet.Unroutable6)
}
// PublicQueryFilter returns true if the peer is suspected of being publicly accessible
func PublicQueryFilter(_ interface{}, ai peer.AddrInfo) bool {
if len(ai.Addrs) == 0 {
return false
}
var hasPublicAddr bool
for _, a := range ai.Addrs {
if !isRelayAddr(a) && isPublicAddr(a) {
hasPublicAddr = true
}
}
return hasPublicAddr
}
type hasHost interface {
Host() host.Host
}
var _ QueryFilterFunc = PublicQueryFilter
// PublicRoutingTableFilter allows a peer to be added to the routing table if the connections to that peer indicate
// that it is on a public network
func PublicRoutingTableFilter(dht interface{}, p peer.ID) bool {
d := dht.(hasHost)
conns := d.Host().Network().ConnsToPeer(p)
if len(conns) == 0 {
return false
}
// Do we have a public address for this peer?
id := conns[0].RemotePeer()
known := d.Host().Peerstore().PeerInfo(id)
for _, a := range known.Addrs {
if !isRelayAddr(a) && isPublicAddr(a) {
return true
}
}
return false
}
var _ RouteTableFilterFunc = PublicRoutingTableFilter
// PrivateQueryFilter doens't currently restrict which peers we are willing to query from the local DHT.
func PrivateQueryFilter(_ interface{}, ai peer.AddrInfo) bool {
return len(ai.Addrs) > 0
}
var _ QueryFilterFunc = PrivateQueryFilter
// We call this very frequently but routes can technically change at runtime.
// Cache it for two minutes.
const routerCacheTime = 2 * time.Minute
var routerCache struct {
sync.RWMutex
router routing.Router
expires time.Time
}
func getCachedRouter() routing.Router {
routerCache.RLock()
router := routerCache.router
expires := routerCache.expires
routerCache.RUnlock()
if time.Now().Before(expires) {
return router
}
routerCache.Lock()
defer routerCache.Unlock()
now := time.Now()
if now.Before(routerCache.expires) {
return router
}
routerCache.router, _ = netroute.New()
routerCache.expires = now.Add(routerCacheTime)
return router
}
// PrivateRoutingTableFilter allows a peer to be added to the routing table if the connections to that peer indicate
// that it is on a private network
func PrivateRoutingTableFilter(dht interface{}, p peer.ID) bool {
d := dht.(hasHost)
conns := d.Host().Network().ConnsToPeer(p)
return privRTFilter(d, conns)
}
func privRTFilter(dht interface{}, conns []network.Conn) bool {
d := dht.(hasHost)
h := d.Host()
router := getCachedRouter()
myAdvertisedIPs := make([]net.IP, 0)
for _, a := range h.Addrs() {
if isPublicAddr(a) && !isRelayAddr(a) {
ip, err := manet.ToIP(a)
if err != nil {
continue
}
myAdvertisedIPs = append(myAdvertisedIPs, ip)
}
}
for _, c := range conns {
ra := c.RemoteMultiaddr()
if isPrivateAddr(ra) && !isRelayAddr(ra) {
return true
}
if isPublicAddr(ra) {
ip, err := manet.ToIP(ra)
if err != nil {
continue
}
// if the ip is the same as one of the local host's public advertised IPs - then consider it local
for _, i := range myAdvertisedIPs {
if i.Equal(ip) {
return true
}
if ip.To4() == nil {
if i.To4() == nil && isEUI(ip) && sameV6Net(i, ip) {
return true
}
}
}
// if there's no gateway - a direct host in the OS routing table - then consider it local
// This is relevant in particular to ipv6 networks where the addresses may all be public,
// but the nodes are aware of direct links between each other.
if router != nil {
_, gw, _, err := router.Route(ip)
if gw == nil && err == nil {
return true
}
}
}
}
return false
}
var _ RouteTableFilterFunc = PrivateRoutingTableFilter
func isEUI(ip net.IP) bool {
// per rfc 2373
return len(ip) == net.IPv6len && ip[11] == 0xff && ip[12] == 0xfe
}
func sameV6Net(a, b net.IP) bool {
//lint:ignore SA1021 We're comparing only parts of the IP address here.
return len(a) == net.IPv6len && len(b) == net.IPv6len && bytes.Equal(a[0:8], b[0:8]) //nolint
}
func isRelayAddr(a ma.Multiaddr) bool {
found := false
ma.ForEach(a, func(c ma.Component) bool {
found = c.Protocol().Code == ma.P_CIRCUIT
return !found
})
return found
}
func inAddrRange(ip net.IP, ipnets []*net.IPNet) bool {
for _, ipnet := range ipnets {
if ipnet.Contains(ip) {
return true
}
}
return false
}