Move singbox launch to isolated module. And refactor the runner to use smaller helper functions.

This commit is contained in:
SimplifiedPrivacy 2026-10-22 15:27:38 -04:00
parent 587c72f4c3
commit 19e8a34730
3 changed files with 245 additions and 235 deletions

View file

@ -1,15 +1,17 @@
from core.services.networking.general_connection_tools.location_evaluation import check_if_location_is_up from core.services.networking.systemwide.encrypted_proxy.launch_proxy import launch_encrypted_proxy
from core.services.networking.api_requests.ApiResponseModel import BackoffStrategy # from core.services.networking.general_connection_tools.location_evaluation import check_if_location_is_up
# from core.services.networking.api_requests.ApiResponseModel import BackoffStrategy
from core.services.networking.general_connection_tools.testing_evaluating import system_uses_wireguard_interface from core.services.networking.general_connection_tools.testing_evaluating import system_uses_wireguard_interface
from core.services.networking.systemwide.systemwide_wireguard import establish_system_connection, terminate_system_connection from core.services.networking.systemwide.systemwide_wireguard import establish_system_connection, terminate_system_connection
from core.services.keys_and_verifications.wireguard_keys import register_wireguard_session from core.services.keys_and_verifications.wireguard_keys import register_wireguard_session
from core.services.subscriptions.subscriptions import activate_subscription, get_encrypted_proxy_billing_code from core.services.subscriptions.subscriptions import activate_subscription
from core.services.networking.systemwide.encrypted_proxy.configure_singbox import configure_singbox #, get_encrypted_proxy_billing_code
from core.services.networking.systemwide.encrypted_proxy.singbox_runner import start_singbox # from core.services.networking.systemwide.encrypted_proxy.configure_singbox import configure_singbox
from core.models.Result import Result, ResultError # from core.services.networking.systemwide.encrypted_proxy.singbox_runner import start_singbox
from core.utils.basic_operations.write_or_read_from_json import get_value_from_json_file # from core.models.Result import Result, ResultError
from core.observers.TicketObserver import TicketObserver # from core.utils.basic_operations.write_or_read_from_json import get_value_from_json_file
from core.services.assassin import assassin_tools # from core.observers.TicketObserver import TicketObserver
# from core.services.assassin import assassin_tools
# If refactored to enums: # If refactored to enums:
# from core.models.session.SessionConnection import SessionConnectionTypes # from core.models.session.SessionConnection import SessionConnectionTypes
@ -17,7 +19,7 @@ from core.services.assassin import assassin_tools
# wireguard_types = [SystemConnectionTypes.WIREGUARD, SessionConnectionTypes.WIREGUARD] # wireguard_types = [SystemConnectionTypes.WIREGUARD, SessionConnectionTypes.WIREGUARD]
from core.errors.logger import logger from core.errors.logger import logger
from core.errors.exceptions import * # from core.errors.exceptions import *
from core.errors.exceptions import FirewallError from core.errors.exceptions import FirewallError
from typing import Union, Optional, Callable from typing import Union, Optional, Callable
from core.models.session.SessionProfile import SessionProfile from core.models.session.SessionProfile import SessionProfile
@ -30,10 +32,6 @@ from core.observers.ConnectionObserver import ConnectionObserver
from core.controllers.SystemStateController import SystemStateController from core.controllers.SystemStateController import SystemStateController
from core.observers.TicketObserver import TicketObserver from core.observers.TicketObserver import TicketObserver
import os
QUANTITY_OF_TRIES = 2
def establish_connection( def establish_connection(
profile: Union[SessionProfile, SystemProfile], profile: Union[SessionProfile, SystemProfile],
ignore: tuple[type[Exception]] = (), ignore: tuple[type[Exception]] = (),
@ -42,7 +40,7 @@ def establish_connection(
): ):
"""Establish a connection for the given profile.""" """Establish a connection for the given profile."""
logger.info(f"[CONNECTION] Checking subscription..") logger.info("[CONNECTION] Checking subscription..")
if not profile.assassin: # Note: Assassin does it's own billing. This would be redundant if not profile.assassin: # Note: Assassin does it's own billing. This would be redundant
activate_subscription(profile=profile, connection_observer=connection_observer) activate_subscription(profile=profile, connection_observer=connection_observer)
@ -56,10 +54,10 @@ def establish_connection(
# ========================================= # =========================================
# SOCKS5 & WIREGUARD # SOCKS5 & WIREGUARD
# ========================================= # =========================================
logger.info(f"[CONNECTION] Checking proxy configuration..") logger.info("[CONNECTION] Checking proxy configuration..")
_ensure_proxy_configured(profile, connection_observer) _ensure_proxy_configured(profile, connection_observer)
logger.info(f"[CONNECTION] Checking Wireguard configuration..") logger.info("[CONNECTION] Checking Wireguard configuration..")
if profile.connection.code == "wireguard": if profile.connection.code == "wireguard":
_ensure_wireguard_configured(profile, connection_observer) _ensure_wireguard_configured(profile, connection_observer)
@ -71,81 +69,6 @@ def establish_connection(
return _establish_with_renegotiation(profile, establish_fn, ignore, connection_observer, ticket_observer) return _establish_with_renegotiation(profile, establish_fn, ignore, connection_observer, ticket_observer)
def launch_encrypted_proxy(
profile: Union[SessionProfile, SystemProfile],
connection_observer: Optional[ConnectionObserver]
):
"""
Launch & Setup an Encrypted proxy.
1) Check if the config exists
2) If not, set it up
3) If it does, get the server IP from it
4) Launch singbox with the config & IP
"""
# do they have a proxy config already?
config_path = f'{profile.get_system_config_path()}/proxy.json'
tries = 0
exists_already = os.path.isfile(config_path)
while tries < QUANTITY_OF_TRIES:
tries = tries + 1
if exists_already:
logger.info("The config exists already, attempting to get the IP from it...")
server_ip = get_value_from_json_file(filepath=config_path, category="outbounds", key="server")
else:
server_ip = setup_config_and_return_ip(
profile=profile,
connection_observer=connection_observer
)
print(f"We will be aiming to establish a connection to {server_ip}")
connection_result = start_singbox(
profile_id=profile.id,
server_ip=server_ip,
protocol_choice=profile.connection.code,
connection_observer=connection_observer
)
if connection_result.valid:
return connection_result
else:
logger.info("The existing config was invalid, trying a new config..")
exists_already = False
# removed = delete_file(config_path)
# if not removed:
# return Result(valid=False, error_type=ResultError.FILE_SYSTEM, message="Can't delete an invalid config.")
def setup_config_and_return_ip(
profile: Union[SessionProfile, SystemProfile],
connection_observer: Optional[ConnectionObserver],
) -> str | None:
configured = configure_singbox(
profile=profile,
connection_observer=connection_observer
)
if configured.valid:
server_ip = configured.data
return server_ip
else:
reason = configured.error_type
logger.error(f"Could not setup Singbox config because {configured.error_type} and {configured.message}")
if reason == ResultError.INVALID_API_REPLY:
raise InvalidSubscriptionError()
else:
raise ConnectionError('The connection could not be established.')
return None # redundant
def _ensure_proxy_configured( def _ensure_proxy_configured(
profile: Union[SessionProfile, SystemProfile], profile: Union[SessionProfile, SystemProfile],
connection_observer: Optional[ConnectionObserver], connection_observer: Optional[ConnectionObserver],
@ -156,11 +79,11 @@ def _ensure_proxy_configured(
return return
if not profile.has_proxy_configuration(): if not profile.has_proxy_configuration():
logger.info(f"[CONNECTION] You need a new proxy configuration") logger.info("[CONNECTION] You need a new proxy configuration")
if not profile.assassin: if not profile.assassin:
activate_subscription(profile, connection_observer) activate_subscription(profile, connection_observer)
logger.info(f"[CONNECTION] Getting a proxy config from web service..") logger.info("[CONNECTION] Getting a proxy config from web service..")
proxy_config = ConnectionController.with_preferred_connection( proxy_config = ConnectionController.with_preferred_connection(
profile.subscription.billing_code, profile.subscription.billing_code,
task=WebServiceApiService.get_proxy_configuration, task=WebServiceApiService.get_proxy_configuration,
@ -186,7 +109,7 @@ def _ensure_wireguard_configured(
if profile.type == ProfileType.SYSTEM: if profile.type == ProfileType.SYSTEM:
if system_uses_wireguard_interface() and SystemStateController.exists(): if system_uses_wireguard_interface() and SystemStateController.exists():
logger.info(f"[CONNECTION] There is a systemwide profile that is already is active.") logger.info("[CONNECTION] There is a systemwide profile that is already is active.")
try: try:
terminate_system_connection() terminate_system_connection()
except ConnectionTerminationError as e: except ConnectionTerminationError as e:
@ -194,9 +117,9 @@ def _ensure_wireguard_configured(
if not profile.assassin: if not profile.assassin:
activate_subscription(profile, connection_observer) activate_subscription(profile, connection_observer)
logger.info(f"[CONNECTION] Checking if the profile already has a wg config..") logger.info("[CONNECTION] Checking if the profile already has a wg config..")
if not profile.has_wireguard_configuration(): if not profile.has_wireguard_configuration():
logger.info(f"[CONNECTION] Profile does NOT have WG keys and it needs it. Registering a NEW wg session..") logger.info("[CONNECTION] Profile does NOT have WG keys and it needs it. Registering a NEW wg session..")
registry_result = register_wireguard_session(profile, connection_observer=connection_observer) registry_result = register_wireguard_session(profile, connection_observer=connection_observer)
logger.info(f"registry_result is {registry_result}") logger.info(f"registry_result is {registry_result}")
@ -212,12 +135,12 @@ def _establish_with_renegotiation(
): ):
"""Attempt connection, renegotiate WireGuard once if needed.""" """Attempt connection, renegotiate WireGuard once if needed."""
try: try:
logger.info(f"[CONNECTION] Connecting..") logger.info("[CONNECTION] Connecting..")
return establish_fn(profile, ignore=ignore, connection_observer=connection_observer) return establish_fn(profile, ignore=ignore, connection_observer=connection_observer)
except ConnectionError as e: except ConnectionError as e:
logger.error(str(e)) logger.error(str(e))
if __should_renegotiate(profile): if __should_renegotiate(profile):
logger.info(f"[CONNECTION] Renegotiating a new wg key session with API..") logger.info("[CONNECTION] Renegotiating a new wg key session with API..")
# KEY # KEY
if connection_observer: if connection_observer:
@ -230,7 +153,7 @@ def _establish_with_renegotiation(
return establish_fn(profile, ignore=ignore, connection_observer=connection_observer, ticket_observer=ticket_observer) return establish_fn(profile, ignore=ignore, connection_observer=connection_observer, ticket_observer=ticket_observer)
raise ConnectionError('The connection could not be established.') raise ConnectionError('The connection could not be established.')
except FirewallError: except FirewallError:
logger.error(f"Failed to turn on the firewall, passing the error up..") logger.error("Failed to turn on the firewall, passing the error up..")
raise raise

View file

@ -0,0 +1,84 @@
from core.services.networking.systemwide.encrypted_proxy.configure_singbox import configure_singbox
from core.services.networking.systemwide.encrypted_proxy.singbox_runner import start_singbox
from core.utils.basic_operations.write_or_read_from_json import get_value_from_json_file
from core.observers.ConnectionObserver import ConnectionObserver
from core.models.BaseProfile import BaseProfile as Profile
from core.models.Result import Result, ResultError
from core.errors.logger import logger
from typing import Optional
import os
QUANTITY_OF_TRIES = 2
def get_existing_server_ip(profile: Profile) -> Optional[str]:
"""Attempts to retrieve the server IP from the local config file."""
config_path = f'{profile.get_system_config_path()}/proxy.json'
if os.path.isfile(config_path):
logger.info("Attempting to get the IP from existing config...")
return get_value_from_json_file(filepath=config_path, category="outbounds", key="server")
return None
def refresh_server_config(profile: Profile, connection_observer: ConnectionObserver) -> Result:
"""Fetches a new configuration from the server and returns the IP."""
configured = configure_singbox(profile=profile, connection_observer=connection_observer)
if not configured.valid:
logger.error(configured.message)
return configured
if configured.data:
return Result(valid=True, data=configured.data)
return Result(
valid=False,
error_type=ResultError.MISSING_DATA,
message="Failed to setup a new singbox configuration and extract its IP"
)
def launch_encrypted_proxy(
profile: Profile,
connection_observer: Optional[ConnectionObserver]
) -> Result:
"""Launch & Setup an Encrypted proxy with a single-refresh retry policy."""
server_ip = get_existing_server_ip(profile)
has_refreshed = False # Tracks if we have already fetched a new config this session
tries = 0
while tries < QUANTITY_OF_TRIES:
tries += 1
# 1. Handle missing IP (initial state if no config existed on disk)
if not server_ip:
logger.info("No existing config found. Fetching initial config...")
refresh_result = refresh_server_config(profile, connection_observer)
if not refresh_result.valid:
return refresh_result
server_ip = refresh_result.data
has_refreshed = True
logger.info(f"Attempt {tries}: establishing connection to server {server_ip}")
connection_result = start_singbox(
profile_id=profile.id,
server_ip=server_ip,
protocol_choice=profile.connection.code,
connection_observer=connection_observer
)
if connection_result.valid:
return connection_result
# 2. Handle connection failure
if not has_refreshed:
logger.info("Existing config failed. Fetching a new config before retrying...")
refresh_result = refresh_server_config(profile, connection_observer)
if not refresh_result.valid:
return refresh_result
server_ip = refresh_result.data
has_refreshed = True
else:
logger.info("Connection failed with new config. Retrying connection without re-fetching...")
return Result(valid=False, message=f"Failed to connect after {QUANTITY_OF_TRIES} attempts")

View file

@ -34,6 +34,39 @@ from typing import Optional
QUANTITY_OF_ATTEMPTS = 2 QUANTITY_OF_ATTEMPTS = 2
def verify_connection_integrity(connection_observer: ConnectionObserver, server_ip: str) -> Result:
"""Checks if the connection is active and not leaking."""
ip_result = ip.test(connection_observer)
if not ip_result.valid:
return Result(
valid=False,
error_type=ResultError.CONNECTION,
message="IP address check is unable to connect."
)
observed_ip = ip_result.data
if str(observed_ip) != str(server_ip):
return Result(
valid=False,
error_type=ResultError.LEAK_ISSUE,
message=f"Connection is leaking! Observed {observed_ip} does NOT match intended {server_ip}."
)
return Result(valid=True, data=observed_ip)
def teardown_failed_connection(current_state: SystemState) -> None:
"""Performs a full cleanup: closes the interface and dissolves system state."""
logger.info("Performing full teardown of failed connection...")
closed = orchestrate_closing(current_state=current_state, interface=Constants.SINGBOX_TUN_IF)
if closed.valid:
logger.info("Closed singbox. Performing redundant state removal...")
SystemStateController.dissolve()
def confirm_proxy_started(protocol_choice: str) -> bool: def confirm_proxy_started(protocol_choice: str) -> bool:
SINGBOX_OUTPUT = Constants.SINGBOX_OUTPUT SINGBOX_OUTPUT = Constants.SINGBOX_OUTPUT
@ -190,168 +223,138 @@ def end_singbox() -> Result:
return Result(valid=True) return Result(valid=True)
def prepare_singbox_environment(protocol_choice: str) -> Result:
"""Ends existing processes and clears logs."""
killed = end_singbox()
if not killed.valid:
return killed
wiped = write_string_to_text_file(content_to_write="", file_path=Constants.SINGBOX_OUTPUT)
if not wiped:
error_msg = f"Critical Error wiping log at {Constants.SINGBOX_OUTPUT}."
if protocol_choice == "vless":
logger.error(f"{error_msg} Vless relies on this to detect connection start.")
return Result(valid=False, error_type=ResultError.FILE_SYSTEM, message=error_msg)
logger.error(f"{error_msg} but {protocol_choice} does not require it.")
return Result(valid=True)
def ensure_interface_is_up(process_id: int, connection_observer: Optional[ConnectionObserver]) -> Result:
"""Verifies the TUN interface is actually active."""
if connection_observer:
connection_observer.notify("connecting", "Testing Interface..")
interface_result = interface_tools.get_output(Constants.SINGBOX_TUN_IF)
if not interface_result.valid:
return Result(valid=False, error_type=ResultError.INTERFACE,
message=f"Could not get interface output for PID {process_id}", data=process_id)
time.sleep(1)
if not interface_tools.is_up(interface_result.data):
msg = "Interface is DOWN"
if connection_observer: connection_observer.notify("connecting", msg)
return Result(valid=False, error_type=ResultError.INTERFACE, data=process_id)
return Result(valid=True)
def apply_network_settings(current_state, server_ip, connection_observer) -> Result:
"""Handles Firewall and DNS configuration."""
# 1. Firewall
firewall_result = enable_firewall_w_retry(
interface_name=str(Constants.SINGBOX_TUN_IF),
server_ip=str(server_ip),
internal_subnet=Constants.SINGBOX_INTERNAL_SUBNET,
max_retries=2
)
if not firewall_result.valid:
return Result(valid=False, error_type=ResultError.FIREWALL, message=firewall_result.message)
if connection_observer:
connection_observer.notify("connecting", "Firewall Enabled.")
# 2. DNS
dns_result = set_dns_for_singbox(current_state)
if not dns_result: # Assuming set_dns returns None/False on failure
return Result(valid=False, error_type=ResultError.DNS, message="Failed to set DNS")
if connection_observer:
connection_observer.notify("connecting", "DNS Changed.")
return Result(valid=True)
def start_singbox( def start_singbox(
profile_id: int, profile_id: int,
server_ip: str, server_ip: str,
protocol_choice: str, protocol_choice: str,
connection_observer: Optional[ConnectionObserver] = None connection_observer: Optional[ConnectionObserver] = None
) -> Result: ) -> Result:
""" """Orchestrates the full flow of the Singbox connection."""
Purpose:
Orchestrates the full flow of the Singbox connection. # 1. Validation & Prep
if not all([profile_id, server_ip]):
return Result(valid=False, error_type=ResultError.INVALID_INPUT, message="Invalid inputs")
Steps: prep_result = prepare_singbox_environment(protocol_choice)
1) Validate inputs if not prep_result.valid:
2) Start the binary return prep_result
3) Evaluate if the process id is still up.
4) Check if the interface is up (before we wait for timeout period for output). This gives immediate feedback.
5) For a timeout period, we wait to confirm via singbox's output in a text file.
6) If it's up, setup the State JSON
7) Test the IP address to match the server via an external API
8) Turn on Firewall
9) Turn on DNS
10) Update the State
"""
# ============= INPUT VALIDATION =============
requirements = [profile_id, server_ip]
for each_requirement in requirements:
if each_requirement is None:
return Result(valid=False, error_type=ResultError.INVALID_INPUT, message="Invalid inputs into singbox runner")
logger.info("All requirements met.")
# ========== KILL IT IF ALREADY UP ========== # 2. Launch Binary
killed_pre_existing = end_singbox()
if not killed_pre_existing.valid:
return killed_pre_existing
logger.info("Pre-existing process ended.")
# ========= WIPE PRE-EXISTING LOGS =======
wiped = write_string_to_text_file(content_to_write="", file_path=Constants.SINGBOX_OUTPUT)
if not wiped:
error_msg = f"Critical Error with wiping the log at {Constants.SINGBOX_OUTPUT}."
if protocol_choice == "vless":
logger.error(f"{error_msg} which vless relies upon to see when the connection started.")
return Result(valid=False, error_type=ResultError.FILE_SYSTEM, message=error_msg)
logger.error(f"{error_msg} but that's okay, because {protocol_choice} doesn't use it.")
# ============= START BINARY =============
if connection_observer: if connection_observer:
connection_observer.notify("connecting", "Starting Software..") connection_observer.notify("connecting", "Starting Software..")
launched = _attempt_start_with_retry(profile_id=profile_id, quantity_of_attempts=2) launched = _attempt_start_with_retry(profile_id=profile_id, quantity_of_attempts=2)
if not launched.valid: if not launched.valid:
return launched return launched
process_id = launched.data process_id = launched.data
logger.info(f"Binary started with process id {process_id}.")
# ============= CHECK INTERFACE ============= # 3. Health Checks (Pre-State Cleanup)
if connection_observer: # Note: Since SystemStateController.create hasn't been called yet, we use terminate_pid() for cleanup here.
connection_observer.notify("connecting", "Testing Interface..") health_check = ensure_interface_is_up(process_id, connection_observer)
print(f"The Constants.SINGBOX_TUN_IF is {Constants.SINGBOX_TUN_IF}") if not health_check.valid:
interface_result = interface_tools.get_output(Constants.SINGBOX_TUN_IF) terminate_pid(process_id) # <--- Lightweight cleanup
if not interface_result.valid: return health_check
error_msg = f"We could not get the interface's output, despite having a process id of {process_id}"
logger.error(error_msg)
return Result(valid=False, error_type=ResultError.INTERFACE, message=error_msg, data=process_id)
time.sleep(1) if not confirm_proxy_started(protocol_choice=protocol_choice):
singbox_output = interface_result.data terminate_pid(process_id) # <--- Lightweight cleanup
interface_up = interface_tools.is_up(singbox_output) return Result(valid=False, error_type=ResultError.CONNECTION, message="Singbox output timeout")
logger.info(f"Is the interface up? {interface_up}")
if interface_up:
logger.info("Interface is confirmed to be Up")
if connection_observer:
connection_observer.notify("connecting", "Interface looking healthy..")
else:
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 ==========
its_up = confirm_proxy_started(protocol_choice=protocol_choice)
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: if connection_observer:
connection_observer.notify("connecting", "Software looking healthy so far..") connection_observer.notify("connecting", "Software looking healthy so far..")
# ============= SETUP STATE ============= # 4. State Persistence
# Even if the connection is dead, firewall is off, we want to save the fact we turned Singbox on, before we raise errors.
logger.info("Setting State JSON with INTENDED firewall & Dns settings")
current_state = SystemStateController.create( current_state = SystemStateController.create(
profile_id=profile_id, profile_id=profile_id,
firewalled=True, # intended setting, not result yet firewalled=True,
dns_set=True, # intended setting, not result yet dns_set=True,
protocol=get_protocol_enum(profile_id), protocol=get_protocol_enum(profile_id),
process_id=process_id process_id=process_id
) )
logger.info("State is created.")
# ============= CONFIRM IP ADDRESS ============= # 5. Network Configuration (Post-State Cleanup)
ip_result = ip.test(connection_observer) # From this point forward, we use the full teardown_failed_connection helper
if not ip_result.valid:
logger.error(f"IP address check is unable to connect. Killing Singbox pid {process_id}") net_result = apply_network_settings(current_state, server_ip, connection_observer)
closed = orchestrate_closing(current_state=current_state, interface=Constants.SINGBOX_TUN_IF) if not net_result.valid:
if closed.valid: logger.error(f"Network Config Failed: {net_result.message}")
logger.info("Closed singbox. Redundant state removal now taking place..") teardown_failed_connection(current_state) # <--- Full teardown
# redundant state removal. This is a repeat of the close_Singbox doing it. return net_result
SystemState.dissolve()
# raise ConnectionError("Could not connect with IP adddress check.") # 6. Final IP Integrity Check
return Result(valid=False, error_type=ResultError.CONNECTION, message="Could not connect with IP adddress check.") validation = verify_connection_integrity(connection_observer, server_ip)
if not validation.valid:
logger.error(validation.message)
teardown_failed_connection(current_state) # <--- Full teardown
if validation.error_type == ResultError.LEAK_ISSUE and connection_observer:
connection_observer.notify("connecting", "Failed Connection! Test showed leak!")
return validation
observed_ip_address = ip_result.data # 7. Success
logger.info(f"""
observed_ip_address: {observed_ip_address}
observed_ip_address type: {type(observed_ip_address)}
server_ip: {server_ip}
server_ip type: {type(server_ip)}
""")
if str(observed_ip_address) != str(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)
logger.info("Observed IP matches the intended server IP. Proceeding..")
if connection_observer: if connection_observer:
connection_observer.notify("connecting", f"IP Matched! {server_ip}") connection_observer.notify("connected", "Connected.")
# ============= FIREWALL =============
logger.info(f"Attempting to enable the Firewall for {Constants.SINGBOX_TUN_IF} and {Constants.SINGBOX_INTERNAL_SUBNET}...")
firewall_result = enable_firewall_w_retry( # this function is "generic" as it's protocol neutral
interface_name=str(Constants.SINGBOX_TUN_IF),
server_ip=str(server_ip),
internal_subnet = (Constants.SINGBOX_INTERNAL_SUBNET),
max_retries = 2
)
if not firewall_result.valid:
# Update the state of the failure,
current_state.firewalled = False
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.
Notifications.emit(EventType.SYSTEM_ENABLE, profile_id) Notifications.emit(EventType.SYSTEM_ENABLE, profile_id)
notification = "Connected." Notifications.emit(EventType.STATUS_UPDATE, "Connected.")
Notifications.emit(EventType.STATUS_UPDATE, notification)
# ============= CONCLUSION =============
return Result(valid=True, data=process_id) return Result(valid=True, data=process_id)