1
0

qt network dialog: don't poll Tor socks proxy, but scan on interaction

Polling is introduces spam in Tor logs.
Also, Tor Browser 12.0 apparently has a bug where our polling renders
the socks proxy unusuable after some time.
see https://gitlab.torproject.org/tpo/applications/tor-browser/-/issues/41549

Instead of trying to detect a Tor socks proxy every 10 seconds, we now
run detection when the Qt network dialog gets opened, and also when
the user switches to the "Proxy" tab in the dialog.

fixes https://github.com/spesmilo/electrum/issues/7317
This commit is contained in:
SomberNight
2023-01-02 11:58:16 +00:00
parent e3485de496
commit 253150cb36
2 changed files with 53 additions and 28 deletions

View File

@@ -40,6 +40,7 @@ from electrum import constants, blockchain, util
from electrum.interface import ServerAddr, PREFERRED_NETWORK_PROTOCOL
from electrum.network import Network
from electrum.logging import get_logger
from electrum.util import detect_tor_socks_proxy
from .util import (Buttons, CloseButton, HelpButton, read_QIcon, char_width_in_lineedit,
PasswordLineEdit)
@@ -66,6 +67,11 @@ class NetworkDialog(QDialog, QtEventListener):
self.register_callbacks()
self._cleaned_up = False
def show(self):
super().show()
if td := self.nlayout.td:
td.trigger_rescan()
@qt_event_listener
def on_event_network_updated(self):
self.nlayout.update()
@@ -203,10 +209,11 @@ class NetworkChoiceLayout(object):
self.tor_proxy = None
self.tabs = tabs = QTabWidget()
proxy_tab = QWidget()
self._proxy_tab = proxy_tab = QWidget()
blockchain_tab = QWidget()
tabs.addTab(blockchain_tab, _('Overview'))
tabs.addTab(proxy_tab, _('Proxy'))
tabs.currentChanged.connect(self._on_tab_changed)
fixed_width_hostname = 24 * char_width_in_lineedit()
fixed_width_port = 6 * char_width_in_lineedit()
@@ -417,6 +424,10 @@ class NetworkChoiceLayout(object):
net_params = net_params._replace(proxy=proxy)
self.network.run_from_another_thread(self.network.set_parameters(net_params))
def _on_tab_changed(self):
if self.tabs.currentWidget() is self._proxy_tab:
self.td.trigger_rescan()
def suggest_proxy(self, found_proxy):
if found_proxy is None:
self.tor_cb.hide()
@@ -457,38 +468,25 @@ class TorDetector(QThread):
def __init__(self):
QThread.__init__(self)
self._stop_event = threading.Event()
self._work_to_do_evt = threading.Event()
self._stopping = False
def run(self):
# Probable ports for Tor to listen at
ports = [9050, 9150]
while True:
for p in ports:
net_addr = ("127.0.0.1", p)
if TorDetector.is_tor_port(net_addr):
self.found_proxy.emit(net_addr)
break
else:
self.found_proxy.emit(None)
stopping = self._stop_event.wait(10)
if stopping:
# do rescan
net_addr = detect_tor_socks_proxy()
self.found_proxy.emit(net_addr)
# wait until triggered
self._work_to_do_evt.wait()
self._work_to_do_evt.clear()
if self._stopping:
return
def trigger_rescan(self) -> None:
self._work_to_do_evt.set()
def stop(self):
self._stop_event.set()
self._stopping = True
self._work_to_do_evt.set()
self.exit()
self.wait()
@staticmethod
def is_tor_port(net_addr: Tuple[str, int]) -> bool:
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.settimeout(0.1)
s.connect(net_addr)
# Tor responds uniquely to HTTP-like requests
s.send(b"GET\n")
if b"Tor is not an HTTP Proxy" in s.recv(1024):
return True
except socket.error:
pass
return False

View File

@@ -47,6 +47,7 @@ import random
import secrets
import functools
from abc import abstractmethod, ABC
import socket
import attr
import aiohttp
@@ -1472,6 +1473,32 @@ class NetworkJobOnDefaultServer(Logger, ABC):
return s
def detect_tor_socks_proxy() -> Optional[Tuple[str, int]]:
# Probable ports for Tor to listen at
candidates = [
("127.0.0.1", 9050),
("127.0.0.1", 9150),
]
for net_addr in candidates:
if is_tor_socks_port(*net_addr):
return net_addr
return None
def is_tor_socks_port(host: str, port: int) -> bool:
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.settimeout(0.1)
s.connect((host, port))
# Tor responds uniquely to HTTP-like requests
s.send(b"GET\n")
if b"Tor is not an HTTP Proxy" in s.recv(1024):
return True
except socket.error:
pass
return False
_asyncio_event_loop = None # type: Optional[asyncio.AbstractEventLoop]
def get_asyncio_loop() -> asyncio.AbstractEventLoop:
"""Returns the global asyncio event loop we use."""