Skip to content

Commit 2260665

Browse files
committed
During node discovery, skip those with invalid addresses
Sometimes a NEIGHBOURS response from remote peers will include nodes with a loopback or private network address that is not reachable to us, so we must skip those. We must also skip those on reserved networks or with an unspecified address (0.0.0.0). Closes: #821
1 parent 28f5129 commit 2260665

File tree

4 files changed

+64
-6
lines changed

4 files changed

+64
-6
lines changed

p2p/discovery.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -463,7 +463,7 @@ def recv_pong_v4(self, node: kademlia.Node, payload: Tuple[Any, ...], _: Hash32)
463463
def recv_neighbours_v4(self, node: kademlia.Node, payload: Tuple[Any, ...], _: Hash32) -> None:
464464
# The neighbours payload should have 2 elements: nodes, expiration
465465
nodes, _ = payload
466-
neighbours = _extract_nodes_from_payload(nodes)
466+
neighbours = _extract_nodes_from_payload(node.address, nodes, self.logger)
467467
self.logger.trace('<<< neighbours from %s: %s', node, neighbours)
468468
self.process_neighbours(node, neighbours)
469469

@@ -493,7 +493,7 @@ def send_ping_v4(self, node: kademlia.Node) -> Hash32:
493493
self.send(node, message)
494494
# Return the msg hash, which is used as a token to identify pongs.
495495
token = message[:MAC_SIZE]
496-
self.logger.trace('>>> ping (v4) %s (token == %s)', node, encode_hex(token))
496+
self.logger.debug('>>> ping (v4) %s (token == %s)', node, encode_hex(token))
497497
# XXX: This hack is needed because there are lots of parity 1.10 nodes out there that send
498498
# the wrong token on pong msgs (https://github.com/paritytech/parity/issues/8038). We
499499
# should get rid of this once there are no longer too many parity 1.10 nodes out there.
@@ -694,7 +694,7 @@ def recv_topic_query(self, node: kademlia.Node, payload: Tuple[Any, ...],
694694
def recv_topic_nodes(self, node: kademlia.Node, payload: Tuple[Any, ...],
695695
_: Hash32, _m: bytes) -> None:
696696
echo, raw_nodes = payload
697-
nodes = _extract_nodes_from_payload(raw_nodes)
697+
nodes = _extract_nodes_from_payload(node.address, raw_nodes, self.logger)
698698
self.logger.trace('<<< topic_nodes from %s: %s', node, nodes)
699699
try:
700700
callback = self.topic_nodes_callbacks.get_callback(node)
@@ -1083,11 +1083,16 @@ def __repr__(self) -> str:
10831083

10841084
@to_list
10851085
def _extract_nodes_from_payload(
1086-
payload: List[Tuple[str, str, str, str]]) -> Iterator[kademlia.Node]:
1086+
sender: kademlia.Address,
1087+
payload: List[Tuple[str, str, str, str]],
1088+
logger: TraceLogger) -> Iterator[kademlia.Node]:
10871089
for item in payload:
10881090
ip, udp_port, tcp_port, node_id = item
10891091
address = kademlia.Address.from_endpoint(ip, udp_port, tcp_port)
1090-
yield kademlia.Node(keys.PublicKey(node_id), address)
1092+
if kademlia.check_relayed_addr(sender, address):
1093+
yield kademlia.Node(keys.PublicKey(node_id), address)
1094+
else:
1095+
logger.debug("Skipping invalid address %s relayed by %s", address, sender)
10911096

10921097

10931098
def _get_max_neighbours_per_packet() -> int:

p2p/kademlia.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,22 @@ def __init__(self, ip: str, udp_port: int, tcp_port: int = 0) -> None:
5757
self.tcp_port = tcp_port
5858
self._ip = ipaddress.ip_address(ip)
5959

60+
@property
61+
def is_loopback(self) -> bool:
62+
return self._ip.is_loopback
63+
64+
@property
65+
def is_unspecified(self) -> bool:
66+
return self._ip.is_unspecified
67+
68+
@property
69+
def is_reserved(self) -> bool:
70+
return self._ip.is_reserved
71+
72+
@property
73+
def is_private(self) -> bool:
74+
return self._ip.is_private
75+
6076
@property
6177
def ip(self) -> str:
6278
return str(self._ip)
@@ -302,6 +318,23 @@ def neighbours(self, node_id: int, k: int = k_bucket_size) -> List[Node]:
302318
return sort_by_distance(nodes, node_id)[:k]
303319

304320

321+
def check_relayed_addr(sender: Address, addr: Address) -> bool:
322+
"""Check if an address relayed by the given sender is valid.
323+
324+
Reserved and unspecified addresses are always invalid.
325+
Private addresses are valid if the sender is a private host.
326+
Loopback addresses are valid if the sender is a loopback host.
327+
All other addresses are valid.
328+
"""
329+
if addr.is_unspecified or addr.is_reserved:
330+
return False
331+
if addr.is_private and not sender.is_private:
332+
return False
333+
if addr.is_loopback and not sender.is_loopback:
334+
return False
335+
return True
336+
337+
305338
def binary_get_bucket_for_node(buckets: List[KBucket], node: Node) -> KBucket:
306339
"""Given a list of ordered buckets, returns the bucket for a given node."""
307340
bucket_ends = [bucket.end for bucket in buckets]

tests/p2p/test_discovery.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,8 @@ def _test_find_node_neighbours(use_v5):
8484
for packet in [packet1, packet2]:
8585
node, payload = packet
8686
assert node == bob.this_node
87-
neighbours.extend(discovery._extract_nodes_from_payload(payload[0]))
87+
neighbours.extend(discovery._extract_nodes_from_payload(
88+
node.address, payload[0], bob.logger))
8889
assert len(neighbours) == kademlia.k_bucket_size
8990

9091

tests/p2p/test_kademlia.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,25 @@ def test_compute_shared_prefix_bits():
207207
assert kademlia._compute_shared_prefix_bits(nodes) == kademlia.k_id_size - 3
208208

209209

210+
def test_check_relayed_addr():
211+
public_host = kademlia.Address('8.8.8.8', 80)
212+
local_host = kademlia.Address('127.0.0.1', 80)
213+
assert kademlia.check_relayed_addr(local_host, local_host)
214+
assert not kademlia.check_relayed_addr(public_host, local_host)
215+
216+
private = kademlia.Address('192.168.1.1', 80)
217+
assert kademlia.check_relayed_addr(private, private)
218+
assert not kademlia.check_relayed_addr(public_host, private)
219+
220+
reserved = kademlia.Address('240.0.0.1', 80)
221+
assert not kademlia.check_relayed_addr(local_host, reserved)
222+
assert not kademlia.check_relayed_addr(public_host, reserved)
223+
224+
unspecified = kademlia.Address('0.0.0.0', 80)
225+
assert not kademlia.check_relayed_addr(local_host, unspecified)
226+
assert not kademlia.check_relayed_addr(public_host, unspecified)
227+
228+
210229
def random_pubkey():
211230
pk = int_to_big_endian(random.getrandbits(kademlia.k_pubkey_size))
212231
return keys.PublicKey(b'\x00' * (kademlia.k_pubkey_size // 8 - len(pk)) + pk)

0 commit comments

Comments
 (0)