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) @dataclass(frozen=True)
class Constants: 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_CONTROL_SOCKET_FILENAME: Final[str] = "tor.sock"
TOR_PROCESS_IDENTIFIER_FILENAME: Final[str] = 'tor.pid' TOR_PROCESS_IDENTIFIER_FILENAME: Final[str] = "tor.pid"
TOR_INSTANCE_LOCK_FILENAME: Final[str] = 'lock' TOR_INSTANCE_LOCK_FILENAME: Final[str] = "lock"

View file

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

View file

@ -2,6 +2,7 @@ from concurrent.futures import ThreadPoolExecutor, TimeoutError as FutureTimeout
from essentials.Constants import Constants from essentials.Constants import Constants
from essentials.Errors import TorServiceInitializationError from essentials.Errors import TorServiceInitializationError
from essentials.observers.ConnectionObserver import ConnectionObserver from essentials.observers.ConnectionObserver import ConnectionObserver
from pathlib import Path from pathlib import Path
from typing import Optional from typing import Optional
import os import os
@ -20,30 +21,40 @@ class TorModule:
self.state_home = state_home self.state_home = state_home
self.bootstrap_timeout = Constants.TOR_BOOTSTRAP_TIMEOUT self.bootstrap_timeout = Constants.TOR_BOOTSTRAP_TIMEOUT
self.control_socket_path = f'{self.state_home}/{Constants.TOR_CONTROL_SOCKET_FILENAME}' self.control_socket_path = (
self.process_identifier_path = f'{self.state_home}/{Constants.TOR_PROCESS_IDENTIFIER_FILENAME}' f"{self.state_home}/{Constants.TOR_CONTROL_SOCKET_FILENAME}"
self.instance_lock_path = f'{self.state_home}/{Constants.TOR_INSTANCE_LOCK_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): 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) Path(self.state_home).mkdir(mode=0o700, parents=True, exist_ok=True)
self.stop_service() self.stop_service()
if connection_observer is not None: # if connection_observer is not None:
connection_observer.notify('tor_bootstrapping') # connection_observer.notify("tor_bootstrapping")
with ThreadPoolExecutor(max_workers=1) as executor: with ThreadPoolExecutor(max_workers=1) as executor:
future = executor.submit( future = executor.submit(
stem.process.launch_tor_with_config, stem.process.launch_tor_with_config,
config={ config={
'DataDirectory': self.state_home, "DataDirectory": self.state_home,
'ControlSocket': self.control_socket_path, "ControlSocket": self.control_socket_path,
'PIDFile': self.process_identifier_path, "PIDFile": self.process_identifier_path,
'SocksPort': '0' "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: try:
@ -52,23 +63,28 @@ class TorModule:
except FutureTimeoutError: except FutureTimeoutError:
self.stop_service() self.stop_service()
raise TorServiceInitializationError('The dedicated Tor service could not be initialized.') raise TorServiceInitializationError(
"The dedicated Tor service could not be initialized."
if connection_observer is not None: )
connection_observer.notify('tor_bootstrapped')
# if connection_observer is not None:
# connection_observer.notify("tor_bootstrapped")
print("tor bootstrapped")
try: 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() controller.authenticate()
except (FileNotFoundError, stem.SocketError, TypeError, IndexError): except (FileNotFoundError, stem.SocketError, TypeError, IndexError):
self.stop_service() 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): def stop_service(self):
control_socket_file = Path(self.control_socket_path) control_socket_file = Path(self.control_socket_path)
process_identifier_file = Path(self.process_identifier_path) process_identifier_file = Path(self.process_identifier_path)
instance_lock_file = Path(self.instance_lock_path) instance_lock_file = Path(self.instance_lock_path)
@ -88,6 +104,8 @@ class TorModule:
except ProcessLookupError: except ProcessLookupError:
pass pass
try:
process = psutil.Process(process_identifier) process = psutil.Process(process_identifier)
if process.is_running(): if process.is_running():
@ -100,54 +118,87 @@ class TorModule:
process_identifier_file.unlink(missing_ok=True) process_identifier_file.unlink(missing_ok=True)
instance_lock_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: 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() controller.authenticate()
except (FileNotFoundError, stem.SocketError, TypeError, IndexError): except (FileNotFoundError, stem.SocketError, TypeError, IndexError):
self.start_service(connection_observer=connection_observer) 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() 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)) 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): def destroy_session(self, port_number: int):
print("destroying session")
try: 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() 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: 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] socks_port_numbers = [
controller.set_conf('SocksPort', 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: else:
controller.set_conf('SocksPort', '0') controller.set_conf("SocksPort", "0")
except (FileNotFoundError, stem.SocketError, TypeError, IndexError): except (FileNotFoundError, stem.SocketError, TypeError, IndexError):
pass pass
@staticmethod @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 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,7 +5,7 @@ class BaseObserver:
def subscribe(self, topic, callback): def subscribe(self, topic, callback):
callbacks = getattr(self, f'on_{topic}', None) callbacks = getattr(self, f"on_{topic}", None)
if callbacks is None: if callbacks is None:
return return
@ -14,7 +14,7 @@ class BaseObserver:
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: if callbacks is None:
return return

View file

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

View file

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