dht_filters.go 5.28 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 10

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

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

	ma "github.com/multiformats/go-multiaddr"
16
	manet "github.com/multiformats/go-multiaddr/net"
17 18 19 20 21 22 23 24 25
)

// QueryFilterFunc is a filter applied when considering peers to dial when querying
type QueryFilterFunc func(dht *IpfsDHT, ai peer.AddrInfo) bool

// RouteTableFilterFunc is a filter applied when considering connections to keep in
// the local route table.
type RouteTableFilterFunc func(dht *IpfsDHT, conns []network.Conn) bool

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

61 62 63 64 65 66 67 68
// PublicQueryFilter returns true if the peer is suspected of being publicly accessible
func PublicQueryFilter(_ *IpfsDHT, ai peer.AddrInfo) bool {
	if len(ai.Addrs) == 0 {
		return false
	}

	var hasPublicAddr bool
	for _, a := range ai.Addrs {
69
		if !isRelayAddr(a) && isPublicAddr(a) {
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
			hasPublicAddr = true
		}
	}
	return hasPublicAddr
}

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 *IpfsDHT, conns []network.Conn) bool {
	if len(conns) == 0 {
		return false
	}

	// Do we have a public address for this peer?
	id := conns[0].RemotePeer()
	known := dht.peerstore.PeerInfo(id)
	for _, a := range known.Addrs {
89
		if !isRelayAddr(a) && isPublicAddr(a) {
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105
			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(dht *IpfsDHT, ai peer.AddrInfo) bool {
	return len(ai.Addrs) > 0
}

var _ QueryFilterFunc = PrivateQueryFilter

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

138 139 140
// 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 *IpfsDHT, conns []network.Conn) bool {
Steven Allen's avatar
Steven Allen committed
141
	router := getCachedRouter()
142 143
	myAdvertisedIPs := make([]net.IP, 0)
	for _, a := range dht.Host().Addrs() {
144
		if isPublicAddr(a) && !isRelayAddr(a) {
145 146 147 148 149 150 151 152 153 154
			ip, err := manet.ToIP(a)
			if err != nil {
				continue
			}
			myAdvertisedIPs = append(myAdvertisedIPs, ip)
		}
	}

	for _, c := range conns {
		ra := c.RemoteMultiaddr()
155
		if isPrivateAddr(ra) && !isRelayAddr(ra) {
156 157 158
			return true
		}

159
		if isPublicAddr(ra) {
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
			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
196
	return len(ip) == net.IPv6len && ip[11] == 0xff && ip[12] == 0xfe
197 198 199
}

func sameV6Net(a, b net.IP) bool {
Steven Allen's avatar
Steven Allen committed
200
	return len(a) == net.IPv6len && len(b) == net.IPv6len && bytes.Equal(a[0:8], b[0:8]) //nolint
201 202 203
}

func isRelayAddr(a ma.Multiaddr) bool {
Steven Allen's avatar
Steven Allen committed
204 205 206 207 208 209
	found := false
	ma.ForEach(a, func(c ma.Component) bool {
		found = c.Protocol().Code == ma.P_CIRCUIT
		return !found
	})
	return found
210
}
211 212 213 214 215 216 217 218 219 220

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

	return false
}