From f4520b9e0d2d98edbe6d22bf205a3c06f364a75e Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Mon, 14 Oct 2024 13:32:22 +0200 Subject: [PATCH] network: use TOR stream isolation also refactor, for proxy instantiation, use Network instance, not a proxy dict. --- electrum/interface.py | 9 +++++---- electrum/lntransport.py | 18 ++++++++++++------ electrum/lnworker.py | 6 +++--- electrum/network.py | 6 +++--- electrum/util.py | 13 +++++++++---- tests/test_lntransport.py | 2 +- tests/test_network.py | 4 ++-- 7 files changed, 35 insertions(+), 23 deletions(-) diff --git a/electrum/interface.py b/electrum/interface.py index d2920c177..86aeee6ba 100644 --- a/electrum/interface.py +++ b/electrum/interface.py @@ -44,7 +44,7 @@ from aiorpcx.jsonrpc import JSONRPC, CodeMessageError from aiorpcx.rawsocket import RSClient import certifi -from .util import (ignore_exceptions, log_exceptions, bfh, MySocksProxy, +from .util import (ignore_exceptions, log_exceptions, bfh, ESocksProxy, is_integer, is_non_negative_integer, is_hash256_str, is_hex_str, is_int_or_float, is_non_negative_int_or_float, OldTaskGroup) from . import util @@ -375,7 +375,7 @@ class Interface(Logger): LOGGING_SHORTCUT = 'i' - def __init__(self, *, network: 'Network', server: ServerAddr, proxy: Optional[dict]): + def __init__(self, *, network: 'Network', server: ServerAddr): self.ready = network.asyncio_loop.create_future() self.got_disconnected = asyncio.Event() self.server = server @@ -394,8 +394,9 @@ class Interface(Logger): # addresses...? e.g. 192.168.x.x if util.is_localhost(server.host): self.logger.info(f"looks like localhost: not using proxy for this server") - proxy = None - self.proxy = MySocksProxy.from_proxy_dict(proxy) + self.proxy = None + else: + self.proxy = ESocksProxy.from_network_settings(network) # Latest block header and corresponding height, as claimed by the server. # Note that these values are updated before they are verified. diff --git a/electrum/lntransport.py b/electrum/lntransport.py index b876783d3..0f40daa42 100644 --- a/electrum/lntransport.py +++ b/electrum/lntransport.py @@ -9,7 +9,7 @@ import re import hashlib import asyncio from asyncio import StreamReader, StreamWriter -from typing import Optional +from typing import Optional, TYPE_CHECKING from functools import cached_property from typing import NamedTuple, List, Tuple, Mapping, Optional, TYPE_CHECKING, Union, Dict, Set, Sequence @@ -17,12 +17,18 @@ from aiorpcx import NetAddress import electrum_ecc as ecc from .crypto import sha256, hmac_oneshot, chacha20_poly1305_encrypt, chacha20_poly1305_decrypt, get_ecdh, privkey_to_pubkey -from .util import MySocksProxy +from .util import ESocksProxy + class LightningPeerConnectionClosed(Exception): pass class HandshakeFailed(Exception): pass class ConnStringFormatError(Exception): pass + +if TYPE_CHECKING: + from electrum.network import Network + + class HandshakeState(object): prologue = b"lightning" protocol_name = b"Noise_XK_secp256k1_ChaChaPoly_SHA256" @@ -342,18 +348,18 @@ class LNTransport(LNTransportBase): """Transport initiated by local party.""" def __init__(self, privkey: bytes, peer_addr: LNPeerAddr, *, - proxy: Optional[dict]): + e_proxy: Optional['ESocksProxy']): LNTransportBase.__init__(self) assert type(privkey) is bytes and len(privkey) == 32 self.privkey = privkey self.peer_addr = peer_addr - self.proxy = MySocksProxy.from_proxy_dict(proxy) + self.e_proxy = e_proxy async def handshake(self): - if not self.proxy: + if not self.e_proxy: self.reader, self.writer = await asyncio.open_connection(self.peer_addr.host, self.peer_addr.port) else: - self.reader, self.writer = await self.proxy.open_connection(self.peer_addr.host, self.peer_addr.port) + self.reader, self.writer = await self.e_proxy.open_connection(self.peer_addr.host, self.peer_addr.port) hs = HandshakeState(self.peer_addr.pubkey) # Get a new ephemeral key epriv, epub = create_ephemeral_key() diff --git a/electrum/lnworker.py b/electrum/lnworker.py index b0a895f43..67464b6ea 100644 --- a/electrum/lnworker.py +++ b/electrum/lnworker.py @@ -31,7 +31,7 @@ from electrum_ecc import ecdsa_der_sig_from_ecdsa_sig64 from . import constants, util from . import keystore -from .util import profiler, chunks, OldTaskGroup +from .util import profiler, chunks, OldTaskGroup, ESocksProxy from .invoices import Invoice, PR_UNPAID, PR_EXPIRED, PR_PAID, PR_INFLIGHT, PR_FAILED, PR_ROUTING, LN_EXPIRY_NEVER from .invoices import BaseInvoice from .util import NetworkRetryManager, JsonRPCClient, NotEnoughFunds @@ -346,7 +346,7 @@ class LNWorker(Logger, EventListener, NetworkRetryManager[LNPeerAddr]): if node_id == self.node_keypair.pubkey: raise ErrorAddingPeer("cannot connect to self") transport = LNTransport(self.node_keypair.privkey, peer_addr, - proxy=self.network.proxy) + e_proxy=ESocksProxy.from_network_settings(self.network)) peer = await self._add_peer_from_transport(node_id=node_id, transport=transport) assert peer return peer @@ -3040,7 +3040,7 @@ class LNWallet(LNWorker): async def _request_fclose(addresses): for host, port, timestamp in addresses: peer_addr = LNPeerAddr(host, port, node_id) - transport = LNTransport(privkey, peer_addr, proxy=self.network.proxy) + transport = LNTransport(privkey, peer_addr, e_proxy=ESocksProxy.from_network_settings(self.network)) peer = Peer(self, node_id, transport, is_channel_backup=True) try: async with OldTaskGroup(wait=any) as group: diff --git a/electrum/network.py b/electrum/network.py index 9a990a087..e0a0ff24a 100644 --- a/electrum/network.py +++ b/electrum/network.py @@ -323,7 +323,7 @@ class Network(Logger, NetworkRetryManager[ServerAddr]): self._allowed_protocols = {PREFERRED_NETWORK_PROTOCOL} - self.proxy = None + self.proxy = None # type: Optional[dict] self.is_proxy_tor = None self._init_parameters_from_config() @@ -885,7 +885,7 @@ class Network(Logger, NetworkRetryManager[ServerAddr]): self._set_status(ConnectionState.CONNECTING) self._trying_addr_now(server) - interface = Interface(network=self, server=server, proxy=self.proxy) + interface = Interface(network=self, server=server) # note: using longer timeouts here as DNS can sometimes be slow! timeout = self.get_network_timeout_seconds(NetworkTimeout.Generic) try: @@ -1479,7 +1479,7 @@ class Network(Logger, NetworkRetryManager[ServerAddr]): timeout = self.get_network_timeout_seconds(NetworkTimeout.Urgent) responses = dict() async def get_response(server: ServerAddr): - interface = Interface(network=self, server=server, proxy=self.proxy) + interface = Interface(network=self, server=server) try: await util.wait_for2(interface.ready, timeout) except BaseException as e: diff --git a/electrum/util.py b/electrum/util.py index c480eaee4..abd0f0999 100644 --- a/electrum/util.py +++ b/electrum/util.py @@ -1938,7 +1938,7 @@ class NetworkRetryManager(Generic[_NetAddrType]): self._last_tried_addr.clear() -class MySocksProxy(aiorpcx.SOCKSProxy): +class ESocksProxy(aiorpcx.SOCKSProxy): # note: proxy will not leak DNS as create_connection() # sets (local DNS) resolve=False by default @@ -1952,12 +1952,17 @@ class MySocksProxy(aiorpcx.SOCKSProxy): return reader, writer @classmethod - def from_proxy_dict(cls, proxy: dict = None) -> Optional['MySocksProxy']: - if not proxy: + def from_network_settings(cls, network: Optional['Network']) -> Optional['ESocksProxy']: + if not network or not network.proxy: return None + proxy = network.proxy username, pw = proxy.get('user'), proxy.get('password') if not username or not pw: - auth = None + # is_proxy_tor is tri-state; None indicates it is still probing the proxy to test for TOR + if network.is_proxy_tor: + auth = aiorpcx.socks.SOCKSRandomAuth() + else: + auth = None else: auth = aiorpcx.socks.SOCKSUserAuth(username, pw) addr = aiorpcx.NetAddress(proxy['host'], proxy['port']) diff --git a/tests/test_lntransport.py b/tests/test_lntransport.py index 3b7b7aebf..ac057fbd2 100644 --- a/tests/test_lntransport.py +++ b/tests/test_lntransport.py @@ -78,7 +78,7 @@ class TestLNTransport(ElectrumTestCase): responder_shaked.set() async def connect(port: int): peer_addr = LNPeerAddr('127.0.0.1', port, responder_key.get_public_key_bytes()) - t = LNTransport(initiator_key.get_secret_bytes(), peer_addr, proxy=None) + t = LNTransport(initiator_key.get_secret_bytes(), peer_addr, e_proxy=None) await t.handshake() async with OldTaskGroup() as group: await group.spawn(read_messages(t, messages_sent_by_server)) diff --git a/tests/test_network.py b/tests/test_network.py index 9afdbb769..d2ccdf78e 100644 --- a/tests/test_network.py +++ b/tests/test_network.py @@ -18,14 +18,14 @@ class MockNetwork: def __init__(self): self.asyncio_loop = util.get_asyncio_loop() self.taskgroup = OldTaskGroup() - + self.proxy = None class MockInterface(Interface): def __init__(self, config): self.config = config network = MockNetwork() network.config = config - super().__init__(network=network, server=ServerAddr.from_str('mock-server:50000:t'), proxy=None) + super().__init__(network=network, server=ServerAddr.from_str('mock-server:50000:t')) self.q = asyncio.Queue() self.blockchain = blockchain.Blockchain(config=self.config, forkpoint=0, parent=None, forkpoint_hash=constants.net.GENESIS, prev_hash=None)