Adjusted Tor Module to work within gui and core's new frameworks. As well as changed the dict observer feedback to string

This commit is contained in:
SimplifiedPrivacy 2026-06-23 18:32:27 -04:00
parent f913d8a082
commit fe9a60563b
7 changed files with 98 additions and 44 deletions

View file

@ -6,8 +6,10 @@ import os
@dataclass(frozen=True)
class Constants:
TOR_BOOTSTRAP_TIMEOUT: Final[int] = int(os.environ.get('TOR_BOOTSTRAP_TIMEOUT', '90'))
TOR_BOOTSTRAP_TIMEOUT: Final[int] = int(
os.environ.get("TOR_BOOTSTRAP_TIMEOUT", "90")
)
TOR_CONTROL_SOCKET_FILENAME: Final[str] = 'tor.sock'
TOR_PROCESS_IDENTIFIER_FILENAME: Final[str] = 'tor.pid'
TOR_INSTANCE_LOCK_FILENAME: Final[str] = 'lock'
TOR_CONTROL_SOCKET_FILENAME: Final[str] = "tor.sock"
TOR_PROCESS_IDENTIFIER_FILENAME: Final[str] = "tor.pid"
TOR_INSTANCE_LOCK_FILENAME: Final[str] = "lock"

View file

@ -5,5 +5,6 @@ class CommandNotFoundError(OSError):
self.subject = subject
super().__init__(f"Command '{subject}' could not be found.")
class TorServiceInitializationError(Exception):
pass

View file

@ -1,6 +1,6 @@
class Event:
def __init__(self, subject = None, meta = None):
def __init__(self, subject=None, meta=None):
self.subject = subject
self.meta = meta or {}

View file

@ -2,6 +2,7 @@ from concurrent.futures import ThreadPoolExecutor, TimeoutError as FutureTimeout
from essentials.Constants import Constants
from essentials.Errors import TorServiceInitializationError
from essentials.observers.ConnectionObserver import ConnectionObserver
from pathlib import Path
from typing import Optional
import os
@ -20,30 +21,40 @@ class TorModule:
self.state_home = state_home
self.bootstrap_timeout = Constants.TOR_BOOTSTRAP_TIMEOUT
self.control_socket_path = f'{self.state_home}/{Constants.TOR_CONTROL_SOCKET_FILENAME}'
self.process_identifier_path = f'{self.state_home}/{Constants.TOR_PROCESS_IDENTIFIER_FILENAME}'
self.instance_lock_path = f'{self.state_home}/{Constants.TOR_INSTANCE_LOCK_FILENAME}'
self.control_socket_path = (
f"{self.state_home}/{Constants.TOR_CONTROL_SOCKET_FILENAME}"
)
self.process_identifier_path = (
f"{self.state_home}/{Constants.TOR_PROCESS_IDENTIFIER_FILENAME}"
)
self.instance_lock_path = (
f"{self.state_home}/{Constants.TOR_INSTANCE_LOCK_FILENAME}"
)
def start_service(self, connection_observer: Optional[ConnectionObserver] = None):
print("started tor service")
Path(self.state_home).mkdir(mode=0o700, parents=True, exist_ok=True)
self.stop_service()
if connection_observer is not None:
connection_observer.notify('tor_bootstrapping')
# if connection_observer is not None:
# connection_observer.notify("tor_bootstrapping")
with ThreadPoolExecutor(max_workers=1) as executor:
future = executor.submit(
stem.process.launch_tor_with_config,
config={
'DataDirectory': self.state_home,
'ControlSocket': self.control_socket_path,
'PIDFile': self.process_identifier_path,
'SocksPort': '0'
"DataDirectory": self.state_home,
"ControlSocket": self.control_socket_path,
"PIDFile": self.process_identifier_path,
"SocksPort": "0",
},
init_msg_handler=lambda contents: TorModule.__on_initialization_message(contents, connection_observer)
init_msg_handler=lambda contents: TorModule.__on_initialization_message(
contents, connection_observer
),
)
try:
@ -52,23 +63,28 @@ class TorModule:
except FutureTimeoutError:
self.stop_service()
raise TorServiceInitializationError('The dedicated Tor service could not be initialized.')
if connection_observer is not None:
connection_observer.notify('tor_bootstrapped')
raise TorServiceInitializationError(
"The dedicated Tor service could not be initialized."
)
# if connection_observer is not None:
# connection_observer.notify("tor_bootstrapped")
print("tor bootstrapped")
try:
controller = stem.control.Controller.from_socket_file(self.control_socket_path)
controller = stem.control.Controller.from_socket_file(
self.control_socket_path
)
controller.authenticate()
except (FileNotFoundError, stem.SocketError, TypeError, IndexError):
self.stop_service()
raise TorServiceInitializationError('The dedicated Tor service could not be initialized.')
raise TorServiceInitializationError(
"The dedicated Tor service could not be initialized."
)
def stop_service(self):
control_socket_file = Path(self.control_socket_path)
process_identifier_file = Path(self.process_identifier_path)
instance_lock_file = Path(self.instance_lock_path)
@ -88,6 +104,8 @@ class TorModule:
except ProcessLookupError:
pass
try:
process = psutil.Process(process_identifier)
if process.is_running():
@ -100,54 +118,87 @@ class TorModule:
process_identifier_file.unlink(missing_ok=True)
instance_lock_file.unlink(missing_ok=True)
def create_session(self, port_number: int, connection_observer: Optional[ConnectionObserver] = None):
def create_session(
self, port_number: int, connection_observer: Optional[ConnectionObserver] = None
):
print("created a session")
try:
controller = stem.control.Controller.from_socket_file(self.control_socket_path)
controller = stem.control.Controller.from_socket_file(
self.control_socket_path
)
controller.authenticate()
except (FileNotFoundError, stem.SocketError, TypeError, IndexError):
self.start_service(connection_observer=connection_observer)
controller = stem.control.Controller.from_socket_file(self.control_socket_path)
controller = stem.control.Controller.from_socket_file(
self.control_socket_path
)
controller.authenticate()
socks_port_numbers = [str(port_number) for port_number in controller.get_ports('socks')]
socks_port_numbers = [
str(port_number) for port_number in controller.get_ports("socks")
]
socks_port_numbers.append(str(port_number))
controller.set_conf('SocksPort', socks_port_numbers)
controller.set_conf("SocksPort", socks_port_numbers)
def destroy_session(self, port_number: int):
print("destroying session")
try:
controller = stem.control.Controller.from_socket_file(self.control_socket_path)
controller = stem.control.Controller.from_socket_file(
self.control_socket_path
)
controller.authenticate()
socks_port_numbers = [str(port_number) for port_number in controller.get_ports('socks')]
socks_port_numbers = [
str(port_number) for port_number in controller.get_ports("socks")
]
if len(socks_port_numbers) > 1:
socks_port_numbers = [socks_port_number for socks_port_number in socks_port_numbers if socks_port_number != port_number]
controller.set_conf('SocksPort', socks_port_numbers)
socks_port_numbers = [
socks_port_number
for socks_port_number in socks_port_numbers
if socks_port_number != port_number
]
controller.set_conf("SocksPort", socks_port_numbers)
else:
controller.set_conf('SocksPort', '0')
controller.set_conf("SocksPort", "0")
except (FileNotFoundError, stem.SocketError, TypeError, IndexError):
pass
@staticmethod
def __on_initialization_message(contents, connection_observer: Optional[ConnectionObserver] = None):
def __on_initialization_message(
contents, connection_observer: Optional[ConnectionObserver] = None
):
if connection_observer is not None:
if 'Bootstrapped ' in contents:
if "Bootstrapped " in contents:
progress = (m := re.search(r' (\d{1,3})% ', contents)) and int(m.group(1))
# original version,
# progress = (m := re.search(r" (\d{1,3})% ", contents)) and int(
# m.group(1)
# )
# # Search for the pattern in the contents
match = re.search(r" (\d{1,3})% ", contents)
# # Check if we found a match
if match:
# If we found it, get the number and convert to integer
progress = int(match.group(1))
else:
# If we didn't find it, use a default value
progress = 0
progress_string = f"Tor Bootstrap {progress}%"
connection_observer.notify("tor_bootstrap_progressing", progress_string)
connection_observer.notify('tor_bootstrap_progressing', None, dict(
progress=progress
))

View file

@ -5,16 +5,16 @@ class BaseObserver:
def subscribe(self, topic, callback):
callbacks = getattr(self, f'on_{topic}', None)
callbacks = getattr(self, f"on_{topic}", None)
if callbacks is None:
return
callbacks.append(callback)
def notify(self, topic, subject = None, meta = None):
def notify(self, topic, subject=None, meta=None):
callbacks = getattr(self, f'on_{topic}', None)
callbacks = getattr(self, f"on_{topic}", None)
if callbacks is None:
return

View file

@ -7,7 +7,7 @@ class ConnectionService:
def get_random_available_port_number():
socket_instance = socket.socket()
socket_instance.bind(('', 0))
socket_instance.bind(("", 0))
port_number = socket_instance.getsockname()[1]
socket_instance.close()

View file

@ -1,6 +1,6 @@
[project]
name = "sp-essentials"
version = "1.0.0"
version = "1.1.0"
authors = [
{ name = "Simplified Privacy" },
]