nim-eth/eth/p2p/discoveryv5/nodes_verification.nim

93 lines
3.2 KiB
Nim

# Copyright (c) 2022-2024 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
# at your option.
# This file may not be copied, modified, or distributed except according to
# those terms.
{.push raises: [].}
import
std/[sets, net],
results, chronicles, chronos,
../../net/utils,
"."/[node, enr, routing_table]
logScope:
topics = "nodes-verification"
func validIp(sender, address: IpAddress): bool =
let a = initTAddress(address, Port(0))
if a.isGlobalUnicast():
true
else:
let s = initTAddress(sender, Port(0))
if a.isLoopback() and s.isLoopback():
true
elif a.isSiteLocal() and s.isSiteLocal():
true
else:
false
proc verifyNodesRecords(
enrs: openArray[Record], src: Node, nodesLimit: int,
distances: Opt[seq[uint16]]): seq[Node] =
## Verify and convert ENRs to a sequence of nodes. Only ENRs that pass
## verification will be added. ENRs are verified for duplicates, invalid
## addresses and invalid distances if those are specified.
logScope:
sender = src.record.toURI
var seen: HashSet[Node]
var count = 0
for r in enrs:
# Check and allow for processing of maximum `findNodeResultLimit` ENRs
# returned. This limitation is required so no huge lists of invalid ENRs
# are processed for no reason, and for not overwhelming a routing table
# with nodes from a malicious actor.
# The discovery v5 specification specifies no limit on the amount of ENRs
# that can be returned, but clients usually stick with the bucket size limit
# as in original Kademlia. Because of this it is chosen not to fail
# immediately, but still process maximum `findNodeResultLimit`.
if count >= nodesLimit:
debug "Too many ENRs", enrs = enrs.len(), limit = nodesLimit
break
count.inc()
let n = Node.fromRecord(r)
# Check for duplicates in the nodes reply. Duplicates are checked based
# on node id.
if n in seen:
trace "Duplicate node ids", record = n.record.toURI, id = n.id
continue
# Check if the node has an address and if the address is public or from
# the same local network or lo network as the sender. The latter allows
# for local testing.
if not n.address.isSome() or not
validIp(src.address.get().ip, n.address.get().ip):
trace "Invalid ip-address", record = n.record.toURI, node = n
continue
# Check if returned node has one of the requested distances.
if distances.isSome():
# TODO: This is incorrect for custom distances
if (not distances.get().contains(logDistance(n.id, src.id))):
debug "Incorrect distance", record = n.record.toURI
continue
# No check on UDP port and thus any port is allowed, also the so called
# "well-known" ports.
seen.incl(n)
result.add(n)
proc verifyNodesRecords*(
enrs: openArray[Record], src: Node, nodesLimit: int): seq[Node] =
verifyNodesRecords(enrs, src, nodesLimit, Opt.none(seq[uint16]))
proc verifyNodesRecords*(
enrs: openArray[Record], src: Node, nodesLimit: int,
distances: seq[uint16]): seq[Node] =
verifyNodesRecords(enrs, src, nodesLimit, Opt.some(distances))