From 5dc1463862f693b513fcb58f9c2be4376451607c Mon Sep 17 00:00:00 2001 From: SimplifiedPrivacy Date: Thu, 15 Oct 2026 16:20:44 -0400 Subject: [PATCH] Singbox on disable, checks if wireguard is active, and kills it --- core/controllers/ProfileController.py | 27 ++++++++--------- .../profile_state/get_profile_data.py | 15 ++++++++++ core/models/enum_models.py | 2 ++ core/models/system/SystemState.py | 4 +++ .../connection_enable.py | 1 + .../cross_protocol.py | 29 +++++++++++++++++++ .../encrypted_proxy/singbox_runner.py | 20 ++++++++++++- core/services/profiles/profile_connections.py | 22 +++++++++++--- 8 files changed, 102 insertions(+), 18 deletions(-) create mode 100644 core/controllers/profile_state/get_profile_data.py create mode 100644 core/services/networking/general_connection_tools/cross_protocol.py diff --git a/core/controllers/ProfileController.py b/core/controllers/ProfileController.py index d02e255..258c210 100644 --- a/core/controllers/ProfileController.py +++ b/core/controllers/ProfileController.py @@ -1,23 +1,24 @@ from core.services.profiles.ProfileRepository import ProfileRepository -from core.services.networking.systemwide.systemwide_wireguard import terminate_system_connection -from core.services.networking.general_connection_tools.testing_evaluating import system_uses_wireguard_interface -from core.services.networking.general_connection_tools.connection_enable import establish_connection -from core.services.networking.systemwide.systemwide_utils import get_firewall_setting, get_dns_setting -from core.services.networking.systemwide.encrypted_proxy.singbox_runner import end_singbox -from core.services.networking.systemwide import killswitch +# from core.services.networking.systemwide.systemwide_wireguard import terminate_system_connection +# from core.services.networking.general_connection_tools.testing_evaluating import system_uses_wireguard_interface +# from core.services.networking.general_connection_tools.connection_enable import establish_connection +# from core.services.networking.systemwide.systemwide_utils import get_firewall_setting, get_dns_setting +# from core.services.networking.systemwide.encrypted_proxy.singbox_runner import end_singbox +# from core.services.networking.systemwide import killswitch from core.services.subscriptions import subscriptions -from core.errors.exceptions import FirewallError +# from core.errors.exceptions import FirewallError from core.models.Result import Result, ResultError -from core.observers.TicketObserver import TicketObserver +# from core.observers.TicketObserver import TicketObserver # from core.services.assassin.ticket_respawn import respawn_profile # from core.services.assassin import assassin_tools from core.errors.logger import logger -from core.Errors import InvalidSubscriptionError, MissingSubscriptionError, ConnectionTerminationError, ProfileActivationError, ProfileDeactivationError, MissingLocationError, ConnectionUnprotectedError, EndpointVerificationError, ProfileStateConflictError -from core.controllers.ApplicationController import ApplicationController -from core.controllers.ApplicationVersionController import ApplicationVersionController -from core.controllers.SessionStateController import SessionStateController -from core.controllers.SystemStateController import SystemStateController +from core.Errors import InvalidSubscriptionError, MissingSubscriptionError +#, ConnectionTerminationError, ProfileActivationError, ProfileDeactivationError, MissingLocationError, ConnectionUnprotectedError, EndpointVerificationError, ProfileStateConflictError +# from core.controllers.ApplicationController import ApplicationController +# from core.controllers.ApplicationVersionController import ApplicationVersionController +# from core.controllers.SessionStateController import SessionStateController +# from core.controllers.SystemStateController import SystemStateController from core.models.BaseProfile import BaseProfile as Profile from core.models.Subscription import Subscription from core.models.session.SessionProfile import SessionProfile diff --git a/core/controllers/profile_state/get_profile_data.py b/core/controllers/profile_state/get_profile_data.py new file mode 100644 index 0000000..1449305 --- /dev/null +++ b/core/controllers/profile_state/get_profile_data.py @@ -0,0 +1,15 @@ +from core.models.enum_models import ProtocolChoice, valid_enum +from core.controllers.ProfileController import ProfileController + + +def get_protocol_enum(profile_id: int) -> ProtocolChoice|bool: + """Return the Enum for a given Profile's Protocol Choice""" + + profile = ProfileController.get(profile_id) + + protocol_string = profile.connection.code + + if not valid_enum(protocol_string, ProtocolChoice): + return False + else: + return ProtocolChoice(protocol_string) diff --git a/core/models/enum_models.py b/core/models/enum_models.py index b1d0749..eec6796 100644 --- a/core/models/enum_models.py +++ b/core/models/enum_models.py @@ -8,6 +8,8 @@ class ProtocolChoice(Enum): SOCK5_PROXY = "proxy" # MASKED_WIREGUARD = "masked_wireguard" +SINGBOX_PROTOCOLS = [ProtocolChoice.HYSTERIA2, ProtocolChoice.VLESS] + class Currency(Enum): MONERO = "monero" BITCOIN = "bitcoin" diff --git a/core/models/system/SystemState.py b/core/models/system/SystemState.py index 07b49fb..33da171 100644 --- a/core/models/system/SystemState.py +++ b/core/models/system/SystemState.py @@ -1,4 +1,7 @@ +from core.models.enum_models import ProtocolChoice from core.Constants import Constants + +# generic from dataclasses import dataclass from dataclasses_json import dataclass_json from pathlib import Path @@ -14,6 +17,7 @@ class SystemState: profile_id: int firewalled: bool dns_set: bool + protocol: ProtocolChoice process_id: Optional[int] = None def save(self: Self): diff --git a/core/services/networking/general_connection_tools/connection_enable.py b/core/services/networking/general_connection_tools/connection_enable.py index 35e3cd8..158494d 100644 --- a/core/services/networking/general_connection_tools/connection_enable.py +++ b/core/services/networking/general_connection_tools/connection_enable.py @@ -92,6 +92,7 @@ def launch_encrypted_proxy( 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( diff --git a/core/services/networking/general_connection_tools/cross_protocol.py b/core/services/networking/general_connection_tools/cross_protocol.py new file mode 100644 index 0000000..04e468a --- /dev/null +++ b/core/services/networking/general_connection_tools/cross_protocol.py @@ -0,0 +1,29 @@ +from core.models.enum_models import ProtocolChoice, SINGBOX_PROTOCOLS +from core.models.system.SystemState import SystemState +from core.utils.type_checker import type_checker +from core.models.Result import Result +from core.errors.logger import logger + +@type_checker +def kill_any_protocol(current_state: SystemState, active_protocol: ProtocolChoice) -> Result: + """Kill any protocol that's currently active""" + + logger.info(f"Killing the external protocol {active_protocol}") + + firewall = current_state.firewalled + dns_set = current_state.dns_set + + if active_protocol == ProtocolChoice.WIREGUARD: + from core.services.networking.systemwide_wireguard import terminate_system_connection + terminate_system_connection( + firewall_setting=firewall, + dns_setting=dns_set + ) + return Result(valid=True) + + elif active_protocol in SINGBOX_PROTOCOLS: + from core.services.networking.systemwide.encrypted_proxy.singbox_runner import end_singbox + return end_singbox() + + else: + logger.error("Not a supported Systemwide Profile.") diff --git a/core/services/networking/systemwide/encrypted_proxy/singbox_runner.py b/core/services/networking/systemwide/encrypted_proxy/singbox_runner.py index 49d338b..6b85a96 100644 --- a/core/services/networking/systemwide/encrypted_proxy/singbox_runner.py +++ b/core/services/networking/systemwide/encrypted_proxy/singbox_runner.py @@ -1,3 +1,6 @@ +from core.controllers.profile_state.get_profile_data import get_protocol_enum +from core.models.enum_models import ProtocolChoice +from core.services.networking.general_connection_tools.cross_protocol import kill_any_protocol from core.services.networking.systemwide.encrypted_proxy import singbox from core.services.networking.systemwide.encrypted_proxy.close_singbox import orchestrate_closing from core.services.networking.systemwide.general_tools import interface_tools @@ -137,6 +140,9 @@ def end_singbox() -> Result: # get by systemstate: current_state = SystemState.get() + # ------------------ + # KILL ZOMBIE SINGBOX NOT TIED TO PROFILES + # ------------------ if not current_state: # Make sure there's no singbox id: singbox_pid = pid_tools.get_pid_by_app_name(exact_app_name="sing-box") @@ -154,7 +160,18 @@ def end_singbox() -> Result: logger.error(f"Critical Issue! State JSON is blank, but Singbox is still running. {singbox_pid}") - # kill the real singbox + + # ------------------ + # KILL NON-SINGBOX SUCH AS WIREGUARD + # ------------------ + active_protocol = get_protocol_enum(current_state.profile_id) + logger.info(f"State JSON shows there is already an active profile with id {profile_id} and protocol {active_protocol}") + if active_protocol not in SINGBOX_PROTOCOLS: + return kill_external_protocol(current_state=current_state, active_protocol=active_protocol) + + # ------------------ + # KILL THE "REAL" KNOWN ACTIVE SINGBOX PROFILE + # ------------------ try: killed_existing = orchestrate_closing( current_state=current_state, @@ -250,6 +267,7 @@ def start_singbox( # ============= CHECK INTERFACE ============= if connection_observer: connection_observer.notify("connecting", "Testing Interface..") + print(f"The Constants.SINGBOX_TUN_IF is {Constants.SINGBOX_TUN_IF}") 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}" diff --git a/core/services/profiles/profile_connections.py b/core/services/profiles/profile_connections.py index 796b769..9c4a4b9 100644 --- a/core/services/profiles/profile_connections.py +++ b/core/services/profiles/profile_connections.py @@ -1,3 +1,4 @@ +from core.models.enum_models import ProtocolChoice, valid_enum from core.controllers.ConnectionController import ConnectionController from core.controllers.ProfileController import ProfileController from core.services.networking.systemwide.systemwide_wireguard import terminate_system_connection @@ -102,7 +103,7 @@ def enable_session_profile( profile_observer.notify('enabled', profile) ApplicationController.launch(application_version, profile, port_number, asynchronous=asynchronous, profile_observer=profile_observer, ticket_observer=ticket_observer, connection_observer=connection_observer) - return Result(valid=True) + return Result(valid=True, data=profile.type) @@ -125,7 +126,7 @@ def enable_profile( if not ProfileStateConflictError in ignore: raise ProfileStateConflictError('The profile is already enabled or its session was not properly terminated.') else: - ProfileController.disable(profile) + disable_profile(profile) # =========== PRISTINE ============ if pristine: @@ -155,7 +156,7 @@ def enable_profile( if connection_result.valid: if profile_observer is not None: profile_observer.notify('enabled', profile) - return Result(valid=True) + return Result(valid=True, data=profile.type) else: logger.error(f"Couldn't enable the profile: {connection_result.error_type}") error_msg = connection_result.message @@ -311,4 +312,17 @@ def destroy_profile( if profile_observer is not None: profile_observer.notify('destroyed', profile) - return Result(valid=True, message="Deleted Profile") \ No newline at end of file + return Result(valid=True, message="Deleted Profile") + + +def get_protocol_enum(profile_id: int) -> ProtocolChoice|bool: + """Return the Enum for a given Profile's Protocol Choice""" + + profile = ProfileController.get(profile_id) + + protocol_string = profile.connection.code + + if not valid_enum(protocol_string, ProtocolChoice): + return False + else: + return ProtocolChoice(protocol_string) \ No newline at end of file