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:
@@ -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
|
||||
|
||||
@@ -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."""
|
||||
|
||||
Reference in New Issue
Block a user