2021-09-13 12:20:26 +00:00
|
|
|
{.push raises: [Defect].}
|
|
|
|
|
|
|
|
import
|
|
|
|
std/[sets, options],
|
|
|
|
stew/results, stew/shims/net, chronicles, chronos,
|
|
|
|
"."/[node, enr, routing_table]
|
|
|
|
|
|
|
|
logScope:
|
|
|
|
topics = "nodes-verification"
|
|
|
|
|
|
|
|
proc validIp(sender, address: IpAddress): bool =
|
|
|
|
let
|
|
|
|
s = initTAddress(sender, Port(0))
|
|
|
|
a = initTAddress(address, Port(0))
|
|
|
|
if a.isAnyLocal():
|
|
|
|
return false
|
|
|
|
if a.isMulticast():
|
|
|
|
return false
|
|
|
|
if a.isLoopback() and not s.isLoopback():
|
|
|
|
return false
|
|
|
|
if a.isSiteLocal() and not s.isSiteLocal():
|
|
|
|
return false
|
|
|
|
# TODO: Also check for special reserved ip addresses:
|
|
|
|
# https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml
|
|
|
|
# https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml
|
|
|
|
return true
|
|
|
|
|
|
|
|
proc verifyNodesRecords(enrs: openarray[Record], fromNode: Node, nodesLimit: int,
|
|
|
|
distances: Option[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.
|
|
|
|
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
|
|
|
|
# immediatly, but still process maximum `findNodeResultLimit`.
|
|
|
|
if count >= nodesLimit:
|
|
|
|
debug "Too many ENRs", enrs = enrs.len(),
|
|
|
|
limit = nodesLimit, sender = fromNode.record.toURI
|
|
|
|
break
|
|
|
|
|
|
|
|
count.inc()
|
|
|
|
|
|
|
|
let node = newNode(r)
|
|
|
|
if node.isOk():
|
|
|
|
let n = node.get()
|
|
|
|
# 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, sender = fromNode.record.toURI
|
|
|
|
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(fromNode.address.get().ip, n.address.get().ip):
|
|
|
|
trace "Invalid ip-address",
|
|
|
|
record = n.record.toURI, node = n, sender = fromNode.record.toURI
|
|
|
|
continue
|
|
|
|
# Check if returned node has one of the requested distances.
|
|
|
|
if distances.isSome():
|
2021-10-07 14:03:12 +00:00
|
|
|
# TODO: This is incorrect for custom distances
|
|
|
|
if (not distances.get().contains(logDistance(n.id, fromNode.id))):
|
2021-09-13 12:20:26 +00:00
|
|
|
debug "Incorrect distance",
|
|
|
|
record = n.record.toURI, sender = fromNode.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], fromNode: Node, nodesLimit: int): seq[Node] =
|
|
|
|
verifyNodesRecords(enrs, fromNode, nodesLimit, none[seq[uint16]]())
|
|
|
|
|
|
|
|
proc verifyNodesRecords*(enrs: openarray[Record], fromNode: Node, nodesLimit: int, distances: seq[uint16]): seq[Node] =
|
|
|
|
verifyNodesRecords(enrs, fromNode, nodesLimit, some[seq[uint16]](distances))
|