dht_filters.go 5.71 KB
Newer Older
1 2 3 4 5
package dht

import (
	"bytes"
	"net"
Steven Allen's avatar
Steven Allen committed
6 7
	"sync"
	"time"
8

9
	"github.com/libp2p/go-libp2p-core/host"
10 11
	"github.com/libp2p/go-libp2p-core/network"
	"github.com/libp2p/go-libp2p-core/peer"
Steven Allen's avatar
Steven Allen committed
12 13

	"github.com/google/gopacket/routing"
14 15 16
	netroute "github.com/libp2p/go-netroute"

	ma "github.com/multiformats/go-multiaddr"
17
	manet "github.com/multiformats/go-multiaddr/net"
18 19

	dhtcfg "github.com/libp2p/go-libp2p-kad-dht/internal/config"
20 21 22
)

// QueryFilterFunc is a filter applied when considering peers to dial when querying
23
type QueryFilterFunc = dhtcfg.QueryFilterFunc
24 25 26

// RouteTableFilterFunc is a filter applied when considering connections to keep in
// the local route table.
27
type RouteTableFilterFunc = dhtcfg.RouteTableFilterFunc
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
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)
}

64
// PublicQueryFilter returns true if the peer is suspected of being publicly accessible
65
func PublicQueryFilter(_ interface{}, ai peer.AddrInfo) bool {
66 67 68 69 70 71
	if len(ai.Addrs) == 0 {
		return false
	}

	var hasPublicAddr bool
	for _, a := range ai.Addrs {
72
		if !isRelayAddr(a) && isPublicAddr(a) {
73 74 75 76 77 78
			hasPublicAddr = true
		}
	}
	return hasPublicAddr
}

79 80 81 82
type hasHost interface {
	Host() host.Host
}

83 84 85 86
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
87 88 89 90
func PublicRoutingTableFilter(dht interface{}, p peer.ID) bool {
	d := dht.(hasHost)

	conns := d.Host().Network().ConnsToPeer(p)
91 92 93 94 95 96
	if len(conns) == 0 {
		return false
	}

	// Do we have a public address for this peer?
	id := conns[0].RemotePeer()
97
	known := d.Host().Peerstore().PeerInfo(id)
98
	for _, a := range known.Addrs {
99
		if !isRelayAddr(a) && isPublicAddr(a) {
100 101 102 103 104 105 106 107 108 109
			return true
		}
	}

	return false
}

var _ RouteTableFilterFunc = PublicRoutingTableFilter

// PrivateQueryFilter doens't currently restrict which peers we are willing to query from the local DHT.
110
func PrivateQueryFilter(_ interface{}, ai peer.AddrInfo) bool {
111 112 113 114 115
	return len(ai.Addrs) > 0
}

var _ QueryFilterFunc = PrivateQueryFilter

Steven Allen's avatar
Steven Allen committed
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
// 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
}

148 149
// 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
150 151 152 153 154 155 156 157 158 159
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()

Steven Allen's avatar
Steven Allen committed
160
	router := getCachedRouter()
161
	myAdvertisedIPs := make([]net.IP, 0)
162
	for _, a := range h.Addrs() {
163
		if isPublicAddr(a) && !isRelayAddr(a) {
164 165 166 167 168 169 170 171 172 173
			ip, err := manet.ToIP(a)
			if err != nil {
				continue
			}
			myAdvertisedIPs = append(myAdvertisedIPs, ip)
		}
	}

	for _, c := range conns {
		ra := c.RemoteMultiaddr()
174
		if isPrivateAddr(ra) && !isRelayAddr(ra) {
175 176 177
			return true
		}

178
		if isPublicAddr(ra) {
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
			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
Steven Allen's avatar
Steven Allen committed
215
	return len(ip) == net.IPv6len && ip[11] == 0xff && ip[12] == 0xfe
216 217 218
}

func sameV6Net(a, b net.IP) bool {
Marten Seemann's avatar
Marten Seemann committed
219
	//lint:ignore SA1021 We're comparing only parts of the IP address here.
Steven Allen's avatar
Steven Allen committed
220
	return len(a) == net.IPv6len && len(b) == net.IPv6len && bytes.Equal(a[0:8], b[0:8]) //nolint
221 222 223
}

func isRelayAddr(a ma.Multiaddr) bool {
Steven Allen's avatar
Steven Allen committed
224 225 226 227 228 229
	found := false
	ma.ForEach(a, func(c ma.Component) bool {
		found = c.Protocol().Code == ma.P_CIRCUIT
		return !found
	})
	return found
230
}
231 232 233 234 235 236 237 238 239 240

func inAddrRange(ip net.IP, ipnets []*net.IPNet) bool {
	for _, ipnet := range ipnets {
		if ipnet.Contains(ip) {
			return true
		}
	}

	return false
}