mirror of
https://github.com/varun-r-mallya/py-libp2p.git
synced 2025-12-31 20:36:24 +00:00
* feat: base implementation of dcutr for hole-punching * chore: removed circuit-relay imports from __init__ * feat: implemented dcutr protocol * added test suite with mock setup * Fix pre-commit hook issues in DCUtR implementation * usages of CONNECT_TYPE and SYNC_TYPE have been replaced with HolePunch.Type.CONNECT and HolePunch.Type.SYNC * added unit tests for dcutr and nat module and * added multiaddr.get_peer_id() with proper DNS address handling and fixed method signature inconsistencies * added assertions to verify DCUtR hole punch result in integration test --------- Co-authored-by: Manu Sheel Gupta <manusheel.edu@gmail.com>
298 lines
9.4 KiB
Python
298 lines
9.4 KiB
Python
"""Tests for NAT traversal utilities."""
|
|
|
|
from unittest.mock import MagicMock
|
|
|
|
import pytest
|
|
from multiaddr import Multiaddr
|
|
|
|
from libp2p.peer.id import ID
|
|
from libp2p.relay.circuit_v2.nat import (
|
|
ReachabilityChecker,
|
|
extract_ip_from_multiaddr,
|
|
ip_to_int,
|
|
is_ip_in_range,
|
|
is_private_ip,
|
|
)
|
|
|
|
|
|
def test_ip_to_int_ipv4():
|
|
"""Test converting IPv4 addresses to integers."""
|
|
assert ip_to_int("192.168.1.1") == 3232235777
|
|
assert ip_to_int("10.0.0.1") == 167772161
|
|
assert ip_to_int("127.0.0.1") == 2130706433
|
|
|
|
|
|
def test_ip_to_int_ipv6():
|
|
"""Test converting IPv6 addresses to integers."""
|
|
# Test with a simple IPv6 address
|
|
ipv6_int = ip_to_int("::1")
|
|
assert isinstance(ipv6_int, int)
|
|
assert ipv6_int > 0
|
|
|
|
|
|
def test_ip_to_int_invalid():
|
|
"""Test handling of invalid IP addresses."""
|
|
with pytest.raises(ValueError):
|
|
ip_to_int("invalid-ip")
|
|
|
|
|
|
def test_is_ip_in_range():
|
|
"""Test IP range checking."""
|
|
# Test within range
|
|
assert is_ip_in_range("192.168.1.5", "192.168.1.1", "192.168.1.10") is True
|
|
assert is_ip_in_range("10.0.0.5", "10.0.0.0", "10.0.0.255") is True
|
|
|
|
# Test outside range
|
|
assert is_ip_in_range("192.168.2.5", "192.168.1.1", "192.168.1.10") is False
|
|
assert is_ip_in_range("8.8.8.8", "10.0.0.0", "10.0.0.255") is False
|
|
|
|
|
|
def test_is_ip_in_range_invalid():
|
|
"""Test IP range checking with invalid inputs."""
|
|
assert is_ip_in_range("invalid", "192.168.1.1", "192.168.1.10") is False
|
|
assert is_ip_in_range("192.168.1.5", "invalid", "192.168.1.10") is False
|
|
|
|
|
|
def test_is_private_ip():
|
|
"""Test private IP detection."""
|
|
# Private IPs
|
|
assert is_private_ip("192.168.1.1") is True
|
|
assert is_private_ip("10.0.0.1") is True
|
|
assert is_private_ip("172.16.0.1") is True
|
|
assert is_private_ip("127.0.0.1") is True # Loopback
|
|
assert is_private_ip("169.254.1.1") is True # Link-local
|
|
|
|
# Public IPs
|
|
assert is_private_ip("8.8.8.8") is False
|
|
assert is_private_ip("1.1.1.1") is False
|
|
assert is_private_ip("208.67.222.222") is False
|
|
|
|
|
|
def test_extract_ip_from_multiaddr():
|
|
"""Test IP extraction from multiaddrs."""
|
|
# IPv4 addresses
|
|
addr1 = Multiaddr("/ip4/192.168.1.1/tcp/1234")
|
|
assert extract_ip_from_multiaddr(addr1) == "192.168.1.1"
|
|
|
|
addr2 = Multiaddr("/ip4/10.0.0.1/udp/5678")
|
|
assert extract_ip_from_multiaddr(addr2) == "10.0.0.1"
|
|
|
|
# IPv6 addresses
|
|
addr3 = Multiaddr("/ip6/::1/tcp/1234")
|
|
assert extract_ip_from_multiaddr(addr3) == "::1"
|
|
|
|
addr4 = Multiaddr("/ip6/2001:db8::1/udp/5678")
|
|
assert extract_ip_from_multiaddr(addr4) == "2001:db8::1"
|
|
|
|
# No IP address
|
|
addr5 = Multiaddr("/dns4/example.com/tcp/1234")
|
|
assert extract_ip_from_multiaddr(addr5) is None
|
|
|
|
# Complex multiaddr (without p2p to avoid base58 issues)
|
|
addr6 = Multiaddr("/ip4/192.168.1.1/tcp/1234/udp/5678")
|
|
assert extract_ip_from_multiaddr(addr6) == "192.168.1.1"
|
|
|
|
|
|
def test_reachability_checker_init():
|
|
"""Test ReachabilityChecker initialization."""
|
|
mock_host = MagicMock()
|
|
checker = ReachabilityChecker(mock_host)
|
|
|
|
assert checker.host == mock_host
|
|
assert checker._peer_reachability == {}
|
|
assert checker._known_public_peers == set()
|
|
|
|
|
|
def test_reachability_checker_is_addr_public():
|
|
"""Test public address detection."""
|
|
mock_host = MagicMock()
|
|
checker = ReachabilityChecker(mock_host)
|
|
|
|
# Public addresses
|
|
public_addr1 = Multiaddr("/ip4/8.8.8.8/tcp/1234")
|
|
assert checker.is_addr_public(public_addr1) is True
|
|
|
|
public_addr2 = Multiaddr("/ip4/1.1.1.1/udp/5678")
|
|
assert checker.is_addr_public(public_addr2) is True
|
|
|
|
# Private addresses
|
|
private_addr1 = Multiaddr("/ip4/192.168.1.1/tcp/1234")
|
|
assert checker.is_addr_public(private_addr1) is False
|
|
|
|
private_addr2 = Multiaddr("/ip4/10.0.0.1/udp/5678")
|
|
assert checker.is_addr_public(private_addr2) is False
|
|
|
|
private_addr3 = Multiaddr("/ip4/127.0.0.1/tcp/1234")
|
|
assert checker.is_addr_public(private_addr3) is False
|
|
|
|
# No IP address
|
|
dns_addr = Multiaddr("/dns4/example.com/tcp/1234")
|
|
assert checker.is_addr_public(dns_addr) is False
|
|
|
|
|
|
def test_reachability_checker_get_public_addrs():
|
|
"""Test filtering for public addresses."""
|
|
mock_host = MagicMock()
|
|
checker = ReachabilityChecker(mock_host)
|
|
|
|
addrs = [
|
|
Multiaddr("/ip4/8.8.8.8/tcp/1234"), # Public
|
|
Multiaddr("/ip4/192.168.1.1/tcp/1234"), # Private
|
|
Multiaddr("/ip4/1.1.1.1/udp/5678"), # Public
|
|
Multiaddr("/ip4/10.0.0.1/tcp/1234"), # Private
|
|
Multiaddr("/dns4/example.com/tcp/1234"), # DNS
|
|
]
|
|
|
|
public_addrs = checker.get_public_addrs(addrs)
|
|
assert len(public_addrs) == 2
|
|
assert Multiaddr("/ip4/8.8.8.8/tcp/1234") in public_addrs
|
|
assert Multiaddr("/ip4/1.1.1.1/udp/5678") in public_addrs
|
|
|
|
|
|
@pytest.mark.trio
|
|
async def test_check_peer_reachability_connected_direct():
|
|
"""Test peer reachability when directly connected."""
|
|
mock_host = MagicMock()
|
|
mock_network = MagicMock()
|
|
mock_host.get_network.return_value = mock_network
|
|
|
|
peer_id = ID(b"test-peer-id")
|
|
mock_conn = MagicMock()
|
|
mock_conn.get_transport_addresses.return_value = [
|
|
Multiaddr("/ip4/192.168.1.1/tcp/1234") # Direct connection
|
|
]
|
|
|
|
mock_network.connections = {peer_id: mock_conn}
|
|
|
|
checker = ReachabilityChecker(mock_host)
|
|
result = await checker.check_peer_reachability(peer_id)
|
|
|
|
assert result is True
|
|
assert checker._peer_reachability[peer_id] is True
|
|
|
|
|
|
@pytest.mark.trio
|
|
async def test_check_peer_reachability_connected_relay():
|
|
"""Test peer reachability when connected through relay."""
|
|
mock_host = MagicMock()
|
|
mock_network = MagicMock()
|
|
mock_host.get_network.return_value = mock_network
|
|
|
|
peer_id = ID(b"test-peer-id")
|
|
mock_conn = MagicMock()
|
|
mock_conn.get_transport_addresses.return_value = [
|
|
Multiaddr("/p2p-circuit/ip4/192.168.1.1/tcp/1234") # Relay connection
|
|
]
|
|
|
|
mock_network.connections = {peer_id: mock_conn}
|
|
|
|
# Mock peerstore with public addresses
|
|
mock_peerstore = MagicMock()
|
|
mock_peerstore.addrs.return_value = [
|
|
Multiaddr("/ip4/8.8.8.8/tcp/1234") # Public address
|
|
]
|
|
mock_host.get_peerstore.return_value = mock_peerstore
|
|
|
|
checker = ReachabilityChecker(mock_host)
|
|
result = await checker.check_peer_reachability(peer_id)
|
|
|
|
assert result is True
|
|
assert checker._peer_reachability[peer_id] is True
|
|
|
|
|
|
@pytest.mark.trio
|
|
async def test_check_peer_reachability_not_connected():
|
|
"""Test peer reachability when not connected."""
|
|
mock_host = MagicMock()
|
|
mock_network = MagicMock()
|
|
mock_host.get_network.return_value = mock_network
|
|
|
|
peer_id = ID(b"test-peer-id")
|
|
mock_network.connections = {} # No connections
|
|
|
|
checker = ReachabilityChecker(mock_host)
|
|
result = await checker.check_peer_reachability(peer_id)
|
|
|
|
assert result is False
|
|
# When not connected, the method doesn't add to cache
|
|
assert peer_id not in checker._peer_reachability
|
|
|
|
|
|
@pytest.mark.trio
|
|
async def test_check_peer_reachability_cached():
|
|
"""Test that peer reachability results are cached."""
|
|
mock_host = MagicMock()
|
|
checker = ReachabilityChecker(mock_host)
|
|
|
|
peer_id = ID(b"test-peer-id")
|
|
checker._peer_reachability[peer_id] = True
|
|
|
|
result = await checker.check_peer_reachability(peer_id)
|
|
assert result is True
|
|
|
|
# Should not call host methods when cached
|
|
mock_host.get_network.assert_not_called()
|
|
|
|
|
|
@pytest.mark.trio
|
|
async def test_check_self_reachability_with_public_addrs():
|
|
"""Test self reachability when host has public addresses."""
|
|
mock_host = MagicMock()
|
|
mock_host.get_addrs.return_value = [
|
|
Multiaddr("/ip4/8.8.8.8/tcp/1234"), # Public
|
|
Multiaddr("/ip4/192.168.1.1/tcp/1234"), # Private
|
|
Multiaddr("/ip4/1.1.1.1/udp/5678"), # Public
|
|
]
|
|
|
|
checker = ReachabilityChecker(mock_host)
|
|
is_reachable, public_addrs = await checker.check_self_reachability()
|
|
|
|
assert is_reachable is True
|
|
assert len(public_addrs) == 2
|
|
assert Multiaddr("/ip4/8.8.8.8/tcp/1234") in public_addrs
|
|
assert Multiaddr("/ip4/1.1.1.1/udp/5678") in public_addrs
|
|
|
|
|
|
@pytest.mark.trio
|
|
async def test_check_self_reachability_no_public_addrs():
|
|
"""Test self reachability when host has no public addresses."""
|
|
mock_host = MagicMock()
|
|
mock_host.get_addrs.return_value = [
|
|
Multiaddr("/ip4/192.168.1.1/tcp/1234"), # Private
|
|
Multiaddr("/ip4/10.0.0.1/udp/5678"), # Private
|
|
Multiaddr("/ip4/127.0.0.1/tcp/1234"), # Loopback
|
|
]
|
|
|
|
checker = ReachabilityChecker(mock_host)
|
|
is_reachable, public_addrs = await checker.check_self_reachability()
|
|
|
|
assert is_reachable is False
|
|
assert len(public_addrs) == 0
|
|
|
|
|
|
@pytest.mark.trio
|
|
async def test_check_peer_reachability_multiple_connections():
|
|
"""Test peer reachability with multiple connections."""
|
|
mock_host = MagicMock()
|
|
mock_network = MagicMock()
|
|
mock_host.get_network.return_value = mock_network
|
|
|
|
peer_id = ID(b"test-peer-id")
|
|
mock_conn1 = MagicMock()
|
|
mock_conn1.get_transport_addresses.return_value = [
|
|
Multiaddr("/p2p-circuit/ip4/192.168.1.1/tcp/1234") # Relay
|
|
]
|
|
|
|
mock_conn2 = MagicMock()
|
|
mock_conn2.get_transport_addresses.return_value = [
|
|
Multiaddr("/ip4/192.168.1.1/tcp/1234") # Direct
|
|
]
|
|
|
|
mock_network.connections = {peer_id: [mock_conn1, mock_conn2]}
|
|
|
|
checker = ReachabilityChecker(mock_host)
|
|
result = await checker.check_peer_reachability(peer_id)
|
|
|
|
assert result is True
|
|
assert checker._peer_reachability[peer_id] is True
|