Merge pull request #2649 from lbryio/invalid-ips

Fix handling reserved ips
This commit is contained in:
Jack Robison 2019-12-01 18:06:32 -05:00 committed by GitHub
commit ea7a31cad1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 81 additions and 30 deletions

View file

@ -15,14 +15,29 @@ log = logging.getLogger(__name__)
@lru_cache(1024) @lru_cache(1024)
def make_kademlia_peer(node_id: typing.Optional[bytes], address: typing.Optional[str], def make_kademlia_peer(node_id: typing.Optional[bytes], address: typing.Optional[str],
udp_port: typing.Optional[int] = None, udp_port: typing.Optional[int] = None,
tcp_port: typing.Optional[int] = None) -> 'KademliaPeer': tcp_port: typing.Optional[int] = None,
return KademliaPeer(address, node_id, udp_port, tcp_port=tcp_port) allow_localhost: bool = False) -> 'KademliaPeer':
return KademliaPeer(address, node_id, udp_port, tcp_port=tcp_port, allow_localhost=allow_localhost)
def is_valid_ipv4(address): # the ipaddress module does not show these subnets as reserved
carrier_grade_NAT_subnet = ipaddress.ip_network('100.64.0.0/10')
ip4_to_6_relay_subnet = ipaddress.ip_network('192.88.99.0/24')
ALLOW_LOCALHOST = False
def is_valid_public_ipv4(address, allow_localhost: bool = False):
allow_localhost = bool(allow_localhost or ALLOW_LOCALHOST)
try: try:
ip = ipaddress.ip_address(address) parsed_ip = ipaddress.ip_address(address)
return ip.version == 4 if parsed_ip.is_loopback and allow_localhost:
return True
return not any((parsed_ip.version != 4, parsed_ip.is_unspecified, parsed_ip.is_link_local,
parsed_ip.is_loopback, parsed_ip.is_multicast, parsed_ip.is_reserved, parsed_ip.is_private,
parsed_ip.is_reserved,
carrier_grade_NAT_subnet.supernet_of(ipaddress.ip_network(f"{address}/32")),
ip4_to_6_relay_subnet.supernet_of(ipaddress.ip_network(f"{address}/32"))))
except ipaddress.AddressValueError: except ipaddress.AddressValueError:
return False return False
@ -151,6 +166,7 @@ class KademliaPeer:
udp_port: typing.Optional[int] = field(hash=True) udp_port: typing.Optional[int] = field(hash=True)
tcp_port: typing.Optional[int] = field(compare=False, hash=False) tcp_port: typing.Optional[int] = field(compare=False, hash=False)
protocol_version: typing.Optional[int] = field(default=1, compare=False, hash=False) protocol_version: typing.Optional[int] = field(default=1, compare=False, hash=False)
allow_localhost: bool = field(default=False, compare=False, hash=False)
def __post_init__(self): def __post_init__(self):
if self._node_id is not None: if self._node_id is not None:
@ -160,7 +176,7 @@ class KademliaPeer:
raise ValueError("invalid udp port") raise ValueError("invalid udp port")
if self.tcp_port is not None and not 1 <= self.tcp_port <= 65535: if self.tcp_port is not None and not 1 <= self.tcp_port <= 65535:
raise ValueError("invalid tcp port") raise ValueError("invalid tcp port")
if not is_valid_ipv4(self.address): if not is_valid_public_ipv4(self.address, self.allow_localhost):
raise ValueError(f"invalid ip address: '{self.address}'") raise ValueError(f"invalid ip address: '{self.address}'")
def update_tcp_port(self, tcp_port: int): def update_tcp_port(self, tcp_port: int):

View file

@ -153,7 +153,11 @@ class IterativeFinder:
self._add_active(peer) self._add_active(peer)
for contact_triple in response.get_close_triples(): for contact_triple in response.get_close_triples():
node_id, address, udp_port = contact_triple node_id, address, udp_port = contact_triple
self._add_active(make_kademlia_peer(node_id, address, udp_port)) try:
self._add_active(make_kademlia_peer(node_id, address, udp_port))
except ValueError:
log.warning("misbehaving peer %s:%i returned peer with reserved ip %s:%i", peer.address,
peer.udp_port, address, udp_port)
self.check_result_ready(response) self.check_result_ready(response)
async def _send_probe(self, peer: 'KademliaPeer'): async def _send_probe(self, peer: 'KademliaPeer'):
@ -328,10 +332,17 @@ class IterativeValueFinder(IterativeFinder):
if not parsed.found: if not parsed.found:
return parsed return parsed
already_known = len(self.discovered_peers[peer]) already_known = len(self.discovered_peers[peer])
self.discovered_peers[peer].update({ decoded_peers = set()
self.peer_manager.decode_tcp_peer_from_compact_address(compact_addr) for compact_addr in parsed.found_compact_addresses:
for compact_addr in parsed.found_compact_addresses try:
}) decoded_peers.add(self.peer_manager.decode_tcp_peer_from_compact_address(compact_addr))
except ValueError:
log.warning("misbehaving peer %s:%i returned invalid peer for blob",
peer.address, peer.udp_port)
self.peer_manager.report_failure(peer.address, peer.udp_port)
parsed.found_compact_addresses.clear()
return parsed
self.discovered_peers[peer].update(decoded_peers)
log.debug("probed %s:%i page %i, %i known", peer.address, peer.udp_port, page, log.debug("probed %s:%i page %i, %i known", peer.address, peer.udp_port, page,
already_known + len(parsed.found_compact_addresses)) already_known + len(parsed.found_compact_addresses))
if len(self.discovered_peers[peer]) != already_known + len(parsed.found_compact_addresses): if len(self.discovered_peers[peer]) != already_known + len(parsed.found_compact_addresses):

View file

@ -12,6 +12,7 @@ from aioupnp.fault import UPnPError
from lbry import utils from lbry import utils
from lbry.dht.node import Node from lbry.dht.node import Node
from lbry.dht.peer import is_valid_public_ipv4
from lbry.dht.blob_announcer import BlobAnnouncer from lbry.dht.blob_announcer import BlobAnnouncer
from lbry.blob.blob_manager import BlobManager from lbry.blob.blob_manager import BlobManager
from lbry.blob_exchange.server import BlobServer from lbry.blob_exchange.server import BlobServer
@ -385,9 +386,8 @@ class UPnPComponent(Component):
log.info("got external ip from UPnP: %s", external_ip) log.info("got external ip from UPnP: %s", external_ip)
except (asyncio.TimeoutError, UPnPError, NotImplementedError): except (asyncio.TimeoutError, UPnPError, NotImplementedError):
pass pass
if external_ip and not is_valid_public_ipv4(external_ip):
if external_ip == "0.0.0.0" or (external_ip and external_ip.startswith("192.")): log.warning("UPnP returned a private/reserved ip - %s, checking lbry.com fallback", external_ip)
log.warning("unable to get external ip from UPnP, checking lbry.com fallback")
external_ip = await utils.get_external_ip() external_ip = await utils.get_external_ip()
if self.external_ip and self.external_ip != external_ip: if self.external_ip and self.external_ip != external_ip:
log.info("external ip changed from %s to %s", self.external_ip, external_ip) log.info("external ip changed from %s to %s", self.external_ip, external_ip)

View file

@ -51,7 +51,7 @@ class StreamDownloader:
def _delayed_add_fixed_peers(): def _delayed_add_fixed_peers():
self.added_fixed_peers = True self.added_fixed_peers = True
self.peer_queue.put_nowait([ self.peer_queue.put_nowait([
make_kademlia_peer(None, address, None, tcp_port=port + 1) make_kademlia_peer(None, address, None, tcp_port=port + 1, allow_localhost=True)
for address, port in addresses for address, port in addresses
]) ])

View file

@ -3,6 +3,7 @@ from binascii import hexlify
from lbry.dht import constants from lbry.dht import constants
from lbry.dht.node import Node from lbry.dht.node import Node
from lbry.dht import peer as dht_peer
from lbry.dht.peer import PeerManager, make_kademlia_peer from lbry.dht.peer import PeerManager, make_kademlia_peer
from torba.testcase import AsyncioTestCase from torba.testcase import AsyncioTestCase
@ -10,6 +11,8 @@ from torba.testcase import AsyncioTestCase
class DHTIntegrationTest(AsyncioTestCase): class DHTIntegrationTest(AsyncioTestCase):
async def asyncSetUp(self): async def asyncSetUp(self):
dht_peer.ALLOW_LOCALHOST = True
self.addCleanup(setattr, dht_peer, 'ALLOW_LOCALHOST', False)
import logging import logging
logging.getLogger('asyncio').setLevel(logging.ERROR) logging.getLogger('asyncio').setLevel(logging.ERROR)
logging.getLogger('lbry.dht').setLevel(logging.WARN) logging.getLogger('lbry.dht').setLevel(logging.WARN)

View file

@ -44,7 +44,7 @@ class BlobExchangeTestBase(AsyncioTestCase):
self.client_storage = SQLiteStorage(self.client_config, os.path.join(self.client_dir, "lbrynet.sqlite")) self.client_storage = SQLiteStorage(self.client_config, os.path.join(self.client_dir, "lbrynet.sqlite"))
self.client_blob_manager = BlobManager(self.loop, self.client_dir, self.client_storage, self.client_config) self.client_blob_manager = BlobManager(self.loop, self.client_dir, self.client_storage, self.client_config)
self.client_peer_manager = PeerManager(self.loop) self.client_peer_manager = PeerManager(self.loop)
self.server_from_client = make_kademlia_peer(b'1' * 48, "127.0.0.1", tcp_port=33333) self.server_from_client = make_kademlia_peer(b'1' * 48, "127.0.0.1", tcp_port=33333, allow_localhost=True)
await self.client_storage.open() await self.client_storage.open()
await self.server_storage.open() await self.server_storage.open()
@ -103,7 +103,7 @@ class TestBlobExchange(BlobExchangeTestBase):
second_client_blob_manager = BlobManager( second_client_blob_manager = BlobManager(
self.loop, second_client_dir, second_client_storage, second_client_conf self.loop, second_client_dir, second_client_storage, second_client_conf
) )
server_from_second_client = make_kademlia_peer(b'1' * 48, "127.0.0.1", tcp_port=33333) server_from_second_client = make_kademlia_peer(b'1' * 48, "127.0.0.1", tcp_port=33333, allow_localhost=True)
await second_client_storage.open() await second_client_storage.open()
await second_client_blob_manager.setup() await second_client_blob_manager.setup()
@ -194,7 +194,7 @@ class TestBlobExchange(BlobExchangeTestBase):
second_client_blob_manager = BlobManager( second_client_blob_manager = BlobManager(
self.loop, second_client_dir, second_client_storage, second_client_conf self.loop, second_client_dir, second_client_storage, second_client_conf
) )
server_from_second_client = make_kademlia_peer(b'1' * 48, "127.0.0.1", tcp_port=33333) server_from_second_client = make_kademlia_peer(b'1' * 48, "127.0.0.1", tcp_port=33333, allow_localhost=True)
await second_client_storage.open() await second_client_storage.open()
await second_client_blob_manager.setup() await second_client_blob_manager.setup()

View file

@ -7,7 +7,7 @@ from lbry.dht import constants
from torba.testcase import AsyncioTestCase from torba.testcase import AsyncioTestCase
def address_generator(address=(10, 42, 42, 1)): def address_generator(address=(1, 2, 3, 4)):
def increment(addr): def increment(addr):
value = struct.unpack("I", "".join([chr(x) for x in list(addr)[::-1]]).encode())[0] + 1 value = struct.unpack("I", "".join([chr(x) for x in list(addr)[::-1]]).encode())[0] + 1
new_addr = [] new_addr = []

View file

@ -119,7 +119,7 @@ class TestBlobAnnouncer(AsyncioTestCase):
async def test_popular_blob(self): async def test_popular_blob(self):
peer_count = 150 peer_count = 150
addresses = [ addresses = [
(constants.generate_id(i + 1), socket.inet_ntoa(int(i + 1).to_bytes(length=4, byteorder='big'))) (constants.generate_id(i + 1), socket.inet_ntoa(int(i + 0x01000001).to_bytes(length=4, byteorder='big')))
for i in range(peer_count) for i in range(peer_count)
] ]
blob_hash = b'1' * 48 blob_hash = b'1' * 48

View file

@ -10,8 +10,8 @@ class PeerTest(AsyncioTestCase):
self.loop = asyncio.get_event_loop() self.loop = asyncio.get_event_loop()
self.peer_manager = PeerManager(self.loop) self.peer_manager = PeerManager(self.loop)
self.node_ids = [generate_id(), generate_id(), generate_id()] self.node_ids = [generate_id(), generate_id(), generate_id()]
self.first_contact = make_kademlia_peer(self.node_ids[1], '127.0.0.1', udp_port=1000) self.first_contact = make_kademlia_peer(self.node_ids[1], '1.0.0.1', udp_port=1000)
self.second_contact = make_kademlia_peer(self.node_ids[0], '192.168.0.1', udp_port=1000) self.second_contact = make_kademlia_peer(self.node_ids[0], '1.0.0.2', udp_port=1000)
def test_peer_is_good_unknown_peer(self): def test_peer_is_good_unknown_peer(self):
# Scenario: peer replied, but caller doesn't know the node_id. # Scenario: peer replied, but caller doesn't know the node_id.
@ -24,21 +24,42 @@ class PeerTest(AsyncioTestCase):
self.assertIsNone(self.peer_manager.peer_is_good(peer)) self.assertIsNone(self.peer_manager.peer_is_good(peer))
def test_make_contact_error_cases(self): def test_make_contact_error_cases(self):
self.assertRaises(ValueError, make_kademlia_peer, self.node_ids[1], '192.168.1.20', 100000) self.assertRaises(ValueError, make_kademlia_peer, self.node_ids[1], '1.2.3.4', 100000)
self.assertRaises(ValueError, make_kademlia_peer, self.node_ids[1], '192.168.1.20.1', 1000) self.assertRaises(ValueError, make_kademlia_peer, self.node_ids[1], '1.2.3.4.5', 1000)
self.assertRaises(ValueError, make_kademlia_peer, self.node_ids[1], 'this is not an ip', 1000) self.assertRaises(ValueError, make_kademlia_peer, self.node_ids[1], 'this is not an ip', 1000)
self.assertRaises(ValueError, make_kademlia_peer, self.node_ids[1], '192.168.1.20', -1000) self.assertRaises(ValueError, make_kademlia_peer, self.node_ids[1], '1.2.3.4', -1000)
self.assertRaises(ValueError, make_kademlia_peer, b'not valid node id', '192.168.1.20', 1000) self.assertRaises(ValueError, make_kademlia_peer, self.node_ids[1], '1.2.3.4', 0)
self.assertRaises(ValueError, make_kademlia_peer, self.node_ids[1], '1.2.3.4', 70000)
self.assertRaises(ValueError, make_kademlia_peer, b'not valid node id', '1.2.3.4', 1000)
self.assertRaises(ValueError, make_kademlia_peer, self.node_ids[1], '0.0.0.0', 1000)
self.assertRaises(ValueError, make_kademlia_peer, self.node_ids[1], '10.0.0.1', 1000)
self.assertRaises(ValueError, make_kademlia_peer, self.node_ids[1], '100.64.0.1', 1000)
self.assertRaises(ValueError, make_kademlia_peer, self.node_ids[1], '127.0.0.1', 1000)
self.assertIsNotNone(make_kademlia_peer(self.node_ids[1], '127.0.0.1', 1000, allow_localhost=True))
self.assertRaises(ValueError, make_kademlia_peer, self.node_ids[1], '192.168.0.1', 1000)
self.assertRaises(ValueError, make_kademlia_peer, self.node_ids[1], '172.16.0.1', 1000)
self.assertRaises(ValueError, make_kademlia_peer, self.node_ids[1], '169.254.1.1', 1000)
self.assertRaises(ValueError, make_kademlia_peer, self.node_ids[1], '192.0.0.2', 1000)
self.assertRaises(ValueError, make_kademlia_peer, self.node_ids[1], '192.0.2.2', 1000)
self.assertRaises(ValueError, make_kademlia_peer, self.node_ids[1], '192.88.99.2', 1000)
self.assertRaises(ValueError, make_kademlia_peer, self.node_ids[1], '198.18.1.1', 1000)
self.assertRaises(ValueError, make_kademlia_peer, self.node_ids[1], '198.51.100.2', 1000)
self.assertRaises(ValueError, make_kademlia_peer, self.node_ids[1], '198.51.100.2', 1000)
self.assertRaises(ValueError, make_kademlia_peer, self.node_ids[1], '203.0.113.4', 1000)
for i in range(32):
self.assertRaises(ValueError, make_kademlia_peer, self.node_ids[1], f"{224 + i}.0.0.0", 1000)
self.assertRaises(ValueError, make_kademlia_peer, self.node_ids[1], '255.255.255.255', 1000)
def test_boolean(self): def test_boolean(self):
self.assertNotEqual(self.first_contact, self.second_contact) self.assertNotEqual(self.first_contact, self.second_contact)
self.assertEqual( self.assertEqual(
self.second_contact, make_kademlia_peer(self.node_ids[0], '192.168.0.1', udp_port=1000) self.second_contact, make_kademlia_peer(self.node_ids[0], '1.0.0.2', udp_port=1000)
) )
def test_compact_ip(self): def test_compact_ip(self):
self.assertEqual(self.first_contact.compact_ip(), b'\x7f\x00\x00\x01') self.assertEqual(b'\x01\x00\x00\x01', self.first_contact.compact_ip())
self.assertEqual(self.second_contact.compact_ip(), b'\xc0\xa8\x00\x01') self.assertEqual(b'\x01\x00\x00\x02', self.second_contact.compact_ip())
@unittest.SkipTest @unittest.SkipTest

View file

@ -111,7 +111,7 @@ class TestManagedStream(BlobExchangeTestBase):
mock_node = mock.Mock(spec=Node) mock_node = mock.Mock(spec=Node)
q = asyncio.Queue() q = asyncio.Queue()
bad_peer = make_kademlia_peer(b'2' * 48, "127.0.0.1", tcp_port=3334) bad_peer = make_kademlia_peer(b'2' * 48, "127.0.0.1", tcp_port=3334, allow_localhost=True)
def _mock_accumulate_peers(q1, q2): def _mock_accumulate_peers(q1, q2):
async def _task(): async def _task():