Singbox on disable, checks if wireguard is active, and kills it

This commit is contained in:
SimplifiedPrivacy 2026-10-15 16:20:44 -04:00
parent 732b17fb4a
commit 5dc1463862
8 changed files with 102 additions and 18 deletions

View file

@ -1,23 +1,24 @@
from core.services.profiles.ProfileRepository import ProfileRepository from core.services.profiles.ProfileRepository import ProfileRepository
from core.services.networking.systemwide.systemwide_wireguard import terminate_system_connection # 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.testing_evaluating import system_uses_wireguard_interface
from core.services.networking.general_connection_tools.connection_enable import establish_connection # 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.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.encrypted_proxy.singbox_runner import end_singbox
from core.services.networking.systemwide import killswitch # from core.services.networking.systemwide import killswitch
from core.services.subscriptions import subscriptions 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.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.ticket_respawn import respawn_profile
# from core.services.assassin import assassin_tools # from core.services.assassin import assassin_tools
from core.errors.logger import logger from core.errors.logger import logger
from core.Errors import InvalidSubscriptionError, MissingSubscriptionError, ConnectionTerminationError, ProfileActivationError, ProfileDeactivationError, MissingLocationError, ConnectionUnprotectedError, EndpointVerificationError, ProfileStateConflictError from core.Errors import InvalidSubscriptionError, MissingSubscriptionError
from core.controllers.ApplicationController import ApplicationController #, ConnectionTerminationError, ProfileActivationError, ProfileDeactivationError, MissingLocationError, ConnectionUnprotectedError, EndpointVerificationError, ProfileStateConflictError
from core.controllers.ApplicationVersionController import ApplicationVersionController # from core.controllers.ApplicationController import ApplicationController
from core.controllers.SessionStateController import SessionStateController # from core.controllers.ApplicationVersionController import ApplicationVersionController
from core.controllers.SystemStateController import SystemStateController # from core.controllers.SessionStateController import SessionStateController
# from core.controllers.SystemStateController import SystemStateController
from core.models.BaseProfile import BaseProfile as Profile from core.models.BaseProfile import BaseProfile as Profile
from core.models.Subscription import Subscription from core.models.Subscription import Subscription
from core.models.session.SessionProfile import SessionProfile from core.models.session.SessionProfile import SessionProfile

View file

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

View file

@ -8,6 +8,8 @@ class ProtocolChoice(Enum):
SOCK5_PROXY = "proxy" SOCK5_PROXY = "proxy"
# MASKED_WIREGUARD = "masked_wireguard" # MASKED_WIREGUARD = "masked_wireguard"
SINGBOX_PROTOCOLS = [ProtocolChoice.HYSTERIA2, ProtocolChoice.VLESS]
class Currency(Enum): class Currency(Enum):
MONERO = "monero" MONERO = "monero"
BITCOIN = "bitcoin" BITCOIN = "bitcoin"

View file

@ -1,4 +1,7 @@
from core.models.enum_models import ProtocolChoice
from core.Constants import Constants from core.Constants import Constants
# generic
from dataclasses import dataclass from dataclasses import dataclass
from dataclasses_json import dataclass_json from dataclasses_json import dataclass_json
from pathlib import Path from pathlib import Path
@ -14,6 +17,7 @@ class SystemState:
profile_id: int profile_id: int
firewalled: bool firewalled: bool
dns_set: bool dns_set: bool
protocol: ProtocolChoice
process_id: Optional[int] = None process_id: Optional[int] = None
def save(self: Self): def save(self: Self):

View file

@ -92,6 +92,7 @@ def launch_encrypted_proxy(
tries = tries + 1 tries = tries + 1
if exists_already: 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") server_ip = get_value_from_json_file(filepath=config_path, category="outbounds", key="server")
else: else:
server_ip = setup_config_and_return_ip( server_ip = setup_config_and_return_ip(

View file

@ -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.")

View file

@ -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 import singbox
from core.services.networking.systemwide.encrypted_proxy.close_singbox import orchestrate_closing from core.services.networking.systemwide.encrypted_proxy.close_singbox import orchestrate_closing
from core.services.networking.systemwide.general_tools import interface_tools from core.services.networking.systemwide.general_tools import interface_tools
@ -137,6 +140,9 @@ def end_singbox() -> Result:
# get by systemstate: # get by systemstate:
current_state = SystemState.get() current_state = SystemState.get()
# ------------------
# KILL ZOMBIE SINGBOX NOT TIED TO PROFILES
# ------------------
if not current_state: if not current_state:
# Make sure there's no singbox id: # Make sure there's no singbox id:
singbox_pid = pid_tools.get_pid_by_app_name(exact_app_name="sing-box") 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}") 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: try:
killed_existing = orchestrate_closing( killed_existing = orchestrate_closing(
current_state=current_state, current_state=current_state,
@ -250,6 +267,7 @@ def start_singbox(
# ============= CHECK INTERFACE ============= # ============= CHECK INTERFACE =============
if connection_observer: if connection_observer:
connection_observer.notify("connecting", "Testing Interface..") 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) interface_result = interface_tools.get_output(Constants.SINGBOX_TUN_IF)
if not interface_result.valid: if not interface_result.valid:
error_msg = f"We could not get the interface's output, despite having a process id of {process_id}" error_msg = f"We could not get the interface's output, despite having a process id of {process_id}"

View file

@ -1,3 +1,4 @@
from core.models.enum_models import ProtocolChoice, valid_enum
from core.controllers.ConnectionController import ConnectionController from core.controllers.ConnectionController import ConnectionController
from core.controllers.ProfileController import ProfileController from core.controllers.ProfileController import ProfileController
from core.services.networking.systemwide.systemwide_wireguard import terminate_system_connection from core.services.networking.systemwide.systemwide_wireguard import terminate_system_connection
@ -102,7 +103,7 @@ def enable_session_profile(
profile_observer.notify('enabled', 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) 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: if not ProfileStateConflictError in ignore:
raise ProfileStateConflictError('The profile is already enabled or its session was not properly terminated.') raise ProfileStateConflictError('The profile is already enabled or its session was not properly terminated.')
else: else:
ProfileController.disable(profile) disable_profile(profile)
# =========== PRISTINE ============ # =========== PRISTINE ============
if pristine: if pristine:
@ -155,7 +156,7 @@ def enable_profile(
if connection_result.valid: if connection_result.valid:
if profile_observer is not None: if profile_observer is not None:
profile_observer.notify('enabled', profile) profile_observer.notify('enabled', profile)
return Result(valid=True) return Result(valid=True, data=profile.type)
else: else:
logger.error(f"Couldn't enable the profile: {connection_result.error_type}") logger.error(f"Couldn't enable the profile: {connection_result.error_type}")
error_msg = connection_result.message error_msg = connection_result.message
@ -312,3 +313,16 @@ def destroy_profile(
profile_observer.notify('destroyed', profile) profile_observer.notify('destroyed', profile)
return Result(valid=True, message="Deleted Profile") 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)