Merge pull request #10448 from f321x/gossip_0126
lnpeermgr: fix proxy check in add_peer, don't connect to onion peers if no proxy
This commit is contained in:
@@ -186,6 +186,9 @@ class LNPeerAddr:
|
||||
def net_addr_str(self) -> str:
|
||||
return str(self._net_addr)
|
||||
|
||||
def is_onion(self) -> bool:
|
||||
return self.host.endswith('.onion')
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, LNPeerAddr):
|
||||
return False
|
||||
|
||||
@@ -483,6 +483,8 @@ class LNPeerManager(Logger, EventListener, NetworkRetryManager[LNPeerAddr]):
|
||||
continue
|
||||
if not self.is_good_peer(peer):
|
||||
continue
|
||||
if peer.is_onion() and not self.network.proxy or not self.network.proxy.enabled:
|
||||
continue
|
||||
return [peer]
|
||||
# try random peer from graph
|
||||
unconnected_nodes = self.channel_db.get_200_randomly_sorted_nodes_not_in(self.peers.keys())
|
||||
@@ -491,7 +493,10 @@ class LNPeerManager(Logger, EventListener, NetworkRetryManager[LNPeerAddr]):
|
||||
addrs = self.channel_db.get_node_addresses(node_id)
|
||||
if not addrs:
|
||||
continue
|
||||
host, port, timestamp = self.choose_preferred_address(list(addrs))
|
||||
address = self.choose_preferred_address(list(addrs))
|
||||
if not address:
|
||||
continue
|
||||
host, port, timestamp = address
|
||||
try:
|
||||
peer = LNPeerAddr(host, port, node_id)
|
||||
except ValueError:
|
||||
@@ -550,15 +555,17 @@ class LNPeerManager(Logger, EventListener, NetworkRetryManager[LNPeerAddr]):
|
||||
self.logger.info(f'got {len(peers)} ln peers from dns seed')
|
||||
return peers
|
||||
|
||||
@staticmethod
|
||||
def choose_preferred_address(addr_list: Sequence[Tuple[str, int, int]]) -> Tuple[str, int, int]:
|
||||
def choose_preferred_address(self, addr_list: Sequence[Tuple[str, int, int]]) -> Optional[Tuple[str, int, int]]:
|
||||
assert len(addr_list) >= 1
|
||||
# choose the most recent one that is an IP
|
||||
for host, port, timestamp in sorted(addr_list, key=lambda a: -a[2]):
|
||||
if is_ip_address(host):
|
||||
return host, port, timestamp
|
||||
if not self.network.proxy or not self.network.proxy.enabled:
|
||||
addr_list = [(h, p, ts) for h, p, ts in addr_list if not h.endswith('.onion')]
|
||||
if not addr_list:
|
||||
return None
|
||||
# otherwise choose one at random
|
||||
# TODO maybe filter out onion if not on tor?
|
||||
choice = random.choice(addr_list)
|
||||
return choice
|
||||
|
||||
@@ -583,12 +590,12 @@ class LNPeerManager(Logger, EventListener, NetworkRetryManager[LNPeerAddr]):
|
||||
host, port = addr.host, addr.port
|
||||
else:
|
||||
addrs = self.channel_db.get_node_addresses(node_id)
|
||||
if not addrs:
|
||||
if not addrs or not (address := self.choose_preferred_address(list(addrs))):
|
||||
raise ConnStringFormatError(_('Don\'t know any addresses for node:') + ' ' + node_id.hex())
|
||||
host, port, timestamp = self.choose_preferred_address(list(addrs))
|
||||
host, port, timestamp = address
|
||||
port = int(port)
|
||||
|
||||
if not self.network.proxy:
|
||||
if not self.network.proxy or not self.network.proxy.enabled:
|
||||
# Try DNS-resolving the host (if needed). This is simply so that
|
||||
# the caller gets a nice exception if it cannot be resolved.
|
||||
# (we don't do the DNS lookup if a proxy is set, to avoid a DNS-leak)
|
||||
|
||||
@@ -26,7 +26,7 @@ from electrum import bitcoin
|
||||
from electrum import util
|
||||
from electrum import constants
|
||||
from electrum import bip32
|
||||
from electrum.network import Network
|
||||
from electrum.network import Network, ProxySettings
|
||||
from electrum import simple_config, lnutil
|
||||
from electrum.lnaddr import lnencode, LnAddr, lndecode
|
||||
from electrum.bitcoin import COIN, sha256
|
||||
@@ -71,6 +71,7 @@ class MockNetwork:
|
||||
self.path_finder = LNPathFinder(self.channel_db)
|
||||
self.lngossip = MockLNGossip()
|
||||
self.tx_queue = asyncio.Queue()
|
||||
self.proxy = ProxySettings()
|
||||
self._blockchain = MockBlockchain()
|
||||
|
||||
def get_local_height(self):
|
||||
|
||||
90
tests/test_lnpeermgr.py
Normal file
90
tests/test_lnpeermgr.py
Normal file
@@ -0,0 +1,90 @@
|
||||
import logging
|
||||
import os
|
||||
import socket
|
||||
import asyncio
|
||||
from unittest import mock
|
||||
|
||||
from . import ElectrumTestCase
|
||||
|
||||
from electrum.lntransport import ConnStringFormatError
|
||||
from electrum.logging import console_stderr_handler
|
||||
|
||||
|
||||
class TestLNPeerManager(ElectrumTestCase):
|
||||
TESTNET = True
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
console_stderr_handler.setLevel(logging.DEBUG)
|
||||
|
||||
async def asyncSetUp(self):
|
||||
lnwallet = self.create_mock_lnwallet(name='mock_lnwallet_anchors', has_anchors=True)
|
||||
self.lnpeermgr = lnwallet.lnpeermgr
|
||||
await super().asyncSetUp()
|
||||
|
||||
async def test_add_peer_conn_string_errors(self):
|
||||
unknown_node_id = os.urandom(33)
|
||||
peermgr = self.lnpeermgr
|
||||
peermgr._add_peer = mock.Mock(side_effect=NotImplementedError)
|
||||
|
||||
# Trampoline enabled, unknown node (no address in trampolines)
|
||||
channel_db = peermgr.network.channel_db
|
||||
peermgr.network.channel_db = None
|
||||
try:
|
||||
with self.assertRaises(ConnStringFormatError) as cm:
|
||||
await peermgr.add_peer(unknown_node_id.hex())
|
||||
self.assertIn("Address unknown for node", str(cm.exception))
|
||||
finally:
|
||||
peermgr.network.channel_db = channel_db # re-set channel db
|
||||
|
||||
# Trampoline disabled, unknown node (no address in channel_db)
|
||||
with mock.patch.object(peermgr.network.channel_db, 'get_node_addresses', return_value=[]):
|
||||
with self.assertRaises(ConnStringFormatError) as cm:
|
||||
await peermgr.add_peer(unknown_node_id.hex())
|
||||
self.assertIn("Don't know any addresses for node", str(cm.exception))
|
||||
|
||||
# .onion address, but no proxy configured
|
||||
onion_conn_str = unknown_node_id.hex() + "@somewhere.onion:9735"
|
||||
self.assertFalse(peermgr.network.proxy.enabled)
|
||||
with self.assertRaises(ConnStringFormatError) as cm:
|
||||
await peermgr.add_peer(onion_conn_str)
|
||||
self.assertIn(".onion address, but no proxy configured", str(cm.exception))
|
||||
|
||||
# Hostname does not resolve (getaddrinfo failed)
|
||||
bad_host_conn_str = unknown_node_id.hex() + "@badhost:9735"
|
||||
loop = asyncio.get_running_loop()
|
||||
with mock.patch.object(loop, 'getaddrinfo', side_effect=socket.gaierror):
|
||||
with self.assertRaises(ConnStringFormatError) as cm:
|
||||
await peermgr.add_peer(bad_host_conn_str)
|
||||
self.assertIn("Hostname does not resolve", str(cm.exception))
|
||||
|
||||
def test_choose_preferred_address(self):
|
||||
peermgr = self.lnpeermgr
|
||||
|
||||
# prefer most recent IP address
|
||||
addr_list = [
|
||||
("192.168.1.1", 9735, 100),
|
||||
("host.onion", 9735, 200),
|
||||
("10.0.0.1", 9735, 150),
|
||||
("host.com", 9735, 250)
|
||||
]
|
||||
result = peermgr.choose_preferred_address(addr_list)
|
||||
self.assertEqual(result, ("10.0.0.1", 9735, 150)) # Most recent IP
|
||||
|
||||
# no IP, proxy disabled, filter .onion and choose random
|
||||
self.assertFalse(peermgr.network.proxy.enabled)
|
||||
addr_list = [("host.com", 9735, 100), ("host.onion", 9735, 200)]
|
||||
result = peermgr.choose_preferred_address(addr_list)
|
||||
self.assertEqual(result, ("host.com", 9735, 100))
|
||||
|
||||
# empty list after filtering
|
||||
addr_list = [("host.onion", 9735, 100)]
|
||||
result = peermgr.choose_preferred_address(addr_list)
|
||||
self.assertIsNone(result)
|
||||
|
||||
# return onion if proxy enabled
|
||||
peermgr.network.proxy.enabled = True
|
||||
addr_list = [("host.onion", 9735, 100)]
|
||||
result = peermgr.choose_preferred_address(addr_list)
|
||||
self.assertEqual(result, ("host.onion", 9735, 100))
|
||||
@@ -141,3 +141,35 @@ class TestLNTransport(ElectrumTestCase):
|
||||
self.assertEqual(extract_nodeid(f"{pubkey1.hex()}@[2001:41d0:e:734::1]:8888"), (pubkey1, "[2001:41d0:e:734::1]:8888"))
|
||||
# just pubkey
|
||||
self.assertEqual(extract_nodeid(f"{pubkey1.hex()}"), (pubkey1, None))
|
||||
|
||||
|
||||
class TestLNPeerAddr(ElectrumTestCase):
|
||||
|
||||
def test_validate_net_address(self):
|
||||
# Test invalid host
|
||||
with self.assertRaises(ValueError):
|
||||
LNPeerAddr("", 9735, b'\x00'*33)
|
||||
with self.assertRaises(ValueError):
|
||||
LNPeerAddr("999.999.999.999", 9735, b'\x00'*33)
|
||||
# Test invalid port
|
||||
with self.assertRaises(ValueError):
|
||||
LNPeerAddr("127.0.0.1", -1, b'\x00'*33)
|
||||
with self.assertRaises(ValueError):
|
||||
LNPeerAddr("127.0.0.1", 70000, b'\x00'*33)
|
||||
|
||||
def test_is_onion(self):
|
||||
# Test onion addresses
|
||||
addr1 = LNPeerAddr("example.onion", 9735, b'\x00'*33)
|
||||
self.assertTrue(addr1.is_onion())
|
||||
addr2 = LNPeerAddr("subdomain.example.onion", 9735, b'\x00'*33)
|
||||
self.assertTrue(addr2.is_onion())
|
||||
|
||||
# Test non-onion
|
||||
addr3 = LNPeerAddr("example.com", 9735, b'\x00'*33)
|
||||
self.assertFalse(addr3.is_onion())
|
||||
addr4 = LNPeerAddr("127.0.0.1", 9735, b'\x00'*33)
|
||||
self.assertFalse(addr4.is_onion())
|
||||
addr5 = LNPeerAddr("::1", 9735, b'\x00'*33)
|
||||
self.assertFalse(addr5.is_onion())
|
||||
addr6 = LNPeerAddr("onion", 9735, b'\x00'*33)
|
||||
self.assertFalse(addr6.is_onion())
|
||||
|
||||
Reference in New Issue
Block a user