Improved Connection Observer Feedback for all systemwide protocols

This commit is contained in:
SimplifiedPrivacy 2026-08-21 15:31:52 -04:00
parent 8968d89c5c
commit a69b87817c
4 changed files with 62 additions and 10 deletions

View file

@ -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:

View file

@ -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:

View file

@ -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 =============

View file

@ -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