From a69b87817c044823f484fa0765a45ee5a65c4686 Mon Sep 17 00:00:00 2001 From: SimplifiedPrivacy Date: Fri, 21 Aug 2026 15:31:52 -0400 Subject: [PATCH] Improved Connection Observer Feedback for all systemwide protocols --- core/controllers/ProfileController.py | 11 +++++-- .../connection_enable.py | 8 +++++ .../encrypted_proxy/singbox_runner.py | 24 +++++++++++++-- .../systemwide/systemwide_wireguard.py | 29 +++++++++++++++---- 4 files changed, 62 insertions(+), 10 deletions(-) diff --git a/core/controllers/ProfileController.py b/core/controllers/ProfileController.py index fa24b50..f99699b 100644 --- a/core/controllers/ProfileController.py +++ b/core/controllers/ProfileController.py @@ -119,9 +119,14 @@ class ProfileController: # ============================================================================ if profile.is_system_profile(): try: - establish_connection(profile, ignore=ignore, connection_observer=connection_observer) - if profile_observer is not None: - profile_observer.notify('enabled', profile) + connection_result = establish_connection(profile, ignore=ignore, connection_observer=connection_observer) + if connection_result.valid: + if profile_observer is not None: + profile_observer.notify('enabled', profile) + else: + logger.error(f"Couldn't enable the profile: {connection_result.error_type}") + error_msg = connection_result.message + raise ProfileActivationError(error_msg) except FirewallError: raise except ConnectionError: diff --git a/core/services/networking/general_connection_tools/connection_enable.py b/core/services/networking/general_connection_tools/connection_enable.py index ef6e76f..ff5904d 100644 --- a/core/services/networking/general_connection_tools/connection_enable.py +++ b/core/services/networking/general_connection_tools/connection_enable.py @@ -193,7 +193,15 @@ def _establish_with_renegotiation( except ConnectionError: if __should_renegotiate(profile): logger.info(f"[CONNECTION] Renegotiating a new wg key session with API..") + + # KEY + if connection_observer: + connection_observer.notify("connecting", "Renegotiating WG Key..") register_wireguard_session(profile, connection_observer=connection_observer) + + # Reconnect: + if connection_observer: + connection_observer.notify("connecting", "Got New WG Key, Connecting..") return establish_fn(profile, ignore=ignore, connection_observer=connection_observer, ticket_observer=ticket_observer) raise ConnectionError('The connection could not be established.') except FirewallError: diff --git a/core/services/networking/systemwide/encrypted_proxy/singbox_runner.py b/core/services/networking/systemwide/encrypted_proxy/singbox_runner.py index 9f33337..50ca5b9 100644 --- a/core/services/networking/systemwide/encrypted_proxy/singbox_runner.py +++ b/core/services/networking/systemwide/encrypted_proxy/singbox_runner.py @@ -238,6 +238,8 @@ def start_singbox( logger.error(f"{error_msg} but that's okay, because {protocol_choice} doesn't use it.") # ============= START BINARY ============= + if connection_observer: + connection_observer.notify("connecting", "Starting Software..") launched = _attempt_start_with_retry(profile_id=profile_id, quantity_of_attempts=2) if not launched.valid: return launched @@ -246,6 +248,8 @@ def start_singbox( logger.info(f"Binary started with process id {process_id}.") # ============= CHECK INTERFACE ============= + if connection_observer: + connection_observer.notify("connecting", "Testing Interface..") interface_result = interface_tools.get_output(Constants.SINGBOX_TUN_IF) if not interface_result.valid: error_msg = f"We could not get the interface's output, despite having a process id of {process_id}" @@ -259,8 +263,13 @@ def start_singbox( if interface_up: logger.info("Interface is confirmed to be Up") + if connection_observer: + connection_observer.notify("connecting", "Interface looking healthy..") else: - logger.error("Interface is DOWN") + error_msg = "Interface is DOWN" + logger.error(error_msg) + if connection_observer: + connection_observer.notify("connecting", error_msg) return Result(valid=False, error_type=ResultError.INTERFACE, data=process_id) # ======= CONFIRM VIA SINGBOX OUTPUT ========== @@ -268,6 +277,8 @@ def start_singbox( if not its_up: return Result(valid=False, error_type=ResultError.CONNECTION, message="After a timeout period, the singbox output is still not showing the connection having started.") logger.info("Singbox output confirming it's up.") + if connection_observer: + connection_observer.notify("connecting", "Software looking healthy so far..") # ============= SETUP STATE ============= # Even if the connection is dead, firewall is off, we want to save the fact we turned Singbox on, before we raise errors. @@ -292,10 +303,15 @@ def start_singbox( observed_ip_address = ip_result.data if observed_ip_address == server_ip: logger.info("Observed IP matches the intended server IP. Proceeding..") + if connection_observer: + connection_observer.notify("connecting", f"IP Matched! {server_ip}") else: - error_msg = f"Critical Leak! IP address check of {observed_ip_address} does NOT match the intended {server_ip}." + error_msg = f"Connection didn't work, it's leaking! IP address check of {observed_ip_address} does NOT match the intended {server_ip}." logger.error(f"{error_msg} Killing Singbox pid {process_id}") closed = orchestrate_closing(current_state=current_state, interface=Constants.SINGBOX_TUN_IF) + error_for_user = f"Failed Connection! Test showed leak! Closed Status: {closed}" + if connection_observer: + connection_observer.notify("connecting", error_for_user) return Result(valid=False, error_type=ResultError.LEAK_ISSUE, message=error_msg) # ============= FIREWALL ============= @@ -313,9 +329,13 @@ def start_singbox( current_state.dns_set = False SystemStateController.update_or_create(current_state) raise FirewallError(firewall_result) # Now end the party. + if connection_observer: + connection_observer.notify("connecting", "Firewall Enabled.") # ============= DNS ============= dns_result = set_dns_for_singbox(current_state) + if dns_result and connection_observer: + connection_observer.notify("connecting", "DNS Changed.") # raises error if not okay. # ============= CONCLUSION ============= diff --git a/core/services/networking/systemwide/systemwide_wireguard.py b/core/services/networking/systemwide/systemwide_wireguard.py index 3eef708..4e35ed1 100644 --- a/core/services/networking/systemwide/systemwide_wireguard.py +++ b/core/services/networking/systemwide/systemwide_wireguard.py @@ -112,6 +112,8 @@ def __establish_system_connection( terminate_system_connection(firewall_setting, dns_setting) # ============= INITIAL NMCLI CONNECTION ============= + if connection_observer: + connection_observer.notify("connecting", "Setting Up WireGuard..") wg_config_path = profile.get_wireguard_configuration_path() nmcli_result = setup_nmcli_connection(wg_config_path) @@ -124,6 +126,8 @@ def __establish_system_connection( raise ConnectionError(f'NMCLI could not be enabled. {nmcli_result.message}') # ============= IPv6 SINKHOLE ============= + if connection_observer: + connection_observer.notify("connecting", "Setting Up IPv6 Protection..") sinkhole_result = setup_ipv6_sinkhole(process_output) if not sinkhole_result: raise ConnectionError('IPv6 Could not be protected.') @@ -139,6 +143,8 @@ def __establish_system_connection( ) # ============= PREP SETTINGS ============= + if connection_observer: + connection_observer.notify("connecting", "Getting Interface Name..") if firewall_setting or dns_setting: wg_interface_result = extract_wg_interface_name(process_output) if not wg_interface_result.valid: @@ -154,6 +160,8 @@ def __establish_system_connection( if firewall_setting: logger.info("Firewall setting is enabled, attempting to enable...") + if connection_observer: + connection_observer.notify("connecting", "Turning Firewall On..") enable_firewall_with_retry( profile_id=str(profile.id), wg_interface_name=wg_interface_name @@ -162,6 +170,8 @@ def __establish_system_connection( # ============= DNS ============= if dns_setting: + if connection_observer: + connection_observer.notify("connecting", "Setting DNS Up..") set_dns_result = set_dns(str(profile.id), wg_interface_name) if not set_dns_result.valid: logger.error(f"[CONNECT] Setting the DNS DIDN'T work. {set_dns_result.message}") @@ -178,6 +188,8 @@ def __establish_system_connection( # ============= TESTING ============= logger.info("If we made it this far, we have nmcli on, ipv6 sinkhole on, firewall on.") try: + if connection_observer: + connection_observer.notify("connecting", "Testing Connection Health..") await_connection(connection_observer=connection_observer) except ConnectionError: @@ -193,11 +205,13 @@ def __establish_system_connection( -def evaluate_connection_result(connection_result: Result) -> None: - """Validate the connection result. Raises if invalid.""" +def evaluate_connection_result(connection_result: Result) -> Result: + """In the future, this will raise other error types or filter replies.""" if not connection_result.valid: logger.error(f"[SYSTEMWIDE WG] Critical issue, could not establish a connection: {connection_result.message}") raise ConnectionError(f"Connection validation failed: {connection_result.message}") + else: + return connection_result def _cleanup_on_error(firewall_setting: bool, dns_setting: bool) -> None: @@ -213,12 +227,14 @@ def _establish_connection_with_retry( firewall_setting: bool, dns_setting: bool, connection_observer: Optional[ConnectionObserver], - max_retries: int = 1 + max_retries: int = 2 ) -> Result: """Establish connection with retry logic for transient errors.""" last_error = None - for attempt in range(1, max_retries + 1): + for attempt in range(1, max_retries): + if connection_observer: + connection_observer.notify("connecting", f"Connecting attempt {attempt} of {max_retries}.. ") try: connection_result = __establish_system_connection( profile=profile, @@ -259,6 +275,8 @@ def establish_system_connection( 3) Retry on failure """ # ================= VERIFY ENDPOINT ================= + if connection_observer: + connection_observer.notify("connecting", "Verifying endpoint..") if ConfigurationController.get_endpoint_verification_enabled(): verify_wireguard_endpoint(profile, ignore=ignore) @@ -268,7 +286,7 @@ def establish_system_connection( # ================= CONNECT WG ================ try: - _establish_connection_with_retry( + final_result = _establish_connection_with_retry( profile=profile, firewall_setting=firewall_setting, dns_setting=dns_setting, @@ -280,3 +298,4 @@ def establish_system_connection( terminate_tor_connection() time.sleep(1.0) + return final_result \ No newline at end of file