refactor(core/cli): route vless/hysteria2 through _enable_encrypted_proxy, scope KillswitchMonitor to encrypted proxy only
This commit is contained in:
parent
cae72efee3
commit
a4bbfa4ec5
6 changed files with 111 additions and 35 deletions
|
|
@ -77,31 +77,14 @@ class ProfileController:
|
|||
|
||||
if profile.is_system_profile():
|
||||
|
||||
if profile.connection.needs_operator_proxy():
|
||||
from core.controllers.ConnectionController import ConnectionController
|
||||
if not profile.has_operator_proxy_session():
|
||||
ProfileController.activate_subscription(profile, connection_observer=connection_observer)
|
||||
from core.services.WebServiceApiService import WebServiceApiService
|
||||
operator_proxy_session = ConnectionController.with_preferred_connection(
|
||||
profile.subscription.billing_code,
|
||||
profile.subscription.operator_id,
|
||||
profile.connection.get_protocol(),
|
||||
task=WebServiceApiService.post_operator_proxy,
|
||||
connection_observer=connection_observer
|
||||
)
|
||||
if operator_proxy_session is None:
|
||||
raise ProfileActivationError('Could not obtain operator proxy session.')
|
||||
profile.attach_operator_proxy_session(operator_proxy_session)
|
||||
|
||||
ok = ConnectionController.establish_encrypted_proxy_connection(
|
||||
profile, socks5_port=1080, observer=connection_observer
|
||||
)
|
||||
if not ok:
|
||||
raise ProfileActivationError('The profile could not be enabled.')
|
||||
if profile.connection.code in ('vless', 'hysteria2'):
|
||||
ProfileController._enable_encrypted_proxy(profile, connection_observer=connection_observer)
|
||||
if profile_observer is not None:
|
||||
profile_observer.notify('enabled', profile)
|
||||
return
|
||||
|
||||
# WireGuard and other system profiles — legacy flow
|
||||
from core.controllers.ConnectionController import ConnectionController
|
||||
try:
|
||||
ConnectionController.establish_connection(profile, ignore=ignore, connection_observer=connection_observer)
|
||||
except ConnectionError:
|
||||
|
|
@ -110,6 +93,37 @@ class ProfileController:
|
|||
if profile_observer is not None:
|
||||
profile_observer.notify('enabled', profile)
|
||||
|
||||
@staticmethod
|
||||
def _enable_encrypted_proxy(profile, connection_observer=None) -> None:
|
||||
|
||||
from core.controllers.ConnectionController import ConnectionController
|
||||
from core.controllers.SystemStateController import SystemStateController
|
||||
from core.services.WebServiceApiService import WebServiceApiService
|
||||
|
||||
if not profile.has_operator_proxy_session():
|
||||
ProfileController.activate_subscription(profile, connection_observer=connection_observer)
|
||||
operator_proxy_session = ConnectionController.with_preferred_connection(
|
||||
profile.subscription.billing_code,
|
||||
profile.subscription.operator_id,
|
||||
profile.connection.code,
|
||||
task=WebServiceApiService.post_operator_proxy,
|
||||
connection_observer=connection_observer
|
||||
)
|
||||
if operator_proxy_session is None:
|
||||
raise ProfileActivationError('Could not obtain operator proxy session.')
|
||||
profile.attach_operator_proxy_session(operator_proxy_session)
|
||||
|
||||
ConnectionController._signal_previous_process()
|
||||
ok = ConnectionController.establish_encrypted_proxy_connection(
|
||||
profile, socks5_port=1080, observer=connection_observer
|
||||
)
|
||||
if not ok:
|
||||
raise ProfileActivationError('The profile could not be enabled.')
|
||||
|
||||
token = SystemStateController.create(profile.id)
|
||||
if connection_observer is not None:
|
||||
connection_observer.notify('connected_token', {'session_token': token})
|
||||
|
||||
@staticmethod
|
||||
def run_monitor(profile: Union[SessionProfile, SystemProfile], monitor) -> None:
|
||||
|
||||
|
|
|
|||
|
|
@ -65,8 +65,8 @@ class BaseProfile(ABC):
|
|||
return type(self).__name__ == 'SystemProfile'
|
||||
|
||||
def save(self: Self):
|
||||
config_dict = self.to_dict() # Get dict, not JSON string
|
||||
location_dict = self.location.to_dict() if self.location else None if self.location else None # this is from SQLAlchemy, and not JSON-models, that's why it's separate.
|
||||
config_dict = self.to_dict()
|
||||
location_dict = self.location.to_dict() if self.location else None
|
||||
|
||||
config_dict["location"] = location_dict
|
||||
|
||||
|
|
|
|||
|
|
@ -209,7 +209,7 @@ class WebServiceApiService:
|
|||
else:
|
||||
headers = None
|
||||
|
||||
return requests.get(Constants.SP_API_BASE_URL + path, headers=headers, proxies=proxies)
|
||||
return requests.get(Constants.SP_API_BASE_URL + path, headers=headers, proxies=proxies, timeout=30)
|
||||
|
||||
@staticmethod
|
||||
def __post(path, billing_code: Optional[str] = None, body: Optional[dict] = None, proxies: Optional[dict] = None):
|
||||
|
|
@ -221,7 +221,7 @@ class WebServiceApiService:
|
|||
else:
|
||||
headers = None
|
||||
|
||||
return requests.post(Constants.SP_API_BASE_URL + path, headers=headers, json=body, proxies=proxies)
|
||||
return requests.post(Constants.SP_API_BASE_URL + path, headers=headers, json=body, proxies=proxies, timeout=30)
|
||||
|
||||
@staticmethod
|
||||
def get_cached_sync(proxies: Optional[dict] = None):
|
||||
|
|
|
|||
|
|
@ -134,10 +134,41 @@ def enable_hysteria(username: str, password: str, server_host: str,
|
|||
_cleanup_all()
|
||||
|
||||
|
||||
def _force_kill_singbox() -> None:
|
||||
import subprocess
|
||||
try:
|
||||
result = subprocess.run(['pgrep', '-x', 'sing-box'], capture_output=True, text=True)
|
||||
if result.returncode == 0:
|
||||
for pid in result.stdout.strip().splitlines():
|
||||
subprocess.run(
|
||||
['sudo', 'kill', '-9', pid.strip()],
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=5
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def disable_hysteria(observer=None) -> bool:
|
||||
revert_dns_on_tun()
|
||||
SingboxRunner().stop()
|
||||
killswitch.disarm()
|
||||
try:
|
||||
revert_dns_on_tun()
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
SingboxRunner().stop()
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
import subprocess, time
|
||||
time.sleep(0.5)
|
||||
result = subprocess.run(['pgrep', '-x', 'sing-box'], capture_output=True, text=True)
|
||||
if result.returncode == 0:
|
||||
_force_kill_singbox()
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
killswitch.disarm()
|
||||
except Exception:
|
||||
pass
|
||||
if observer:
|
||||
observer.notify("disconnected", {"tunnel_if": Constants.SINGBOX_TUN_IF})
|
||||
return True
|
||||
|
|
@ -163,10 +163,41 @@ def enable_vless(vless_link: str, username: str, server_ip: str,
|
|||
_cleanup_all()
|
||||
|
||||
|
||||
def _force_kill_singbox() -> None:
|
||||
import subprocess
|
||||
try:
|
||||
result = subprocess.run(['pgrep', '-x', 'sing-box'], capture_output=True, text=True)
|
||||
if result.returncode == 0:
|
||||
for pid in result.stdout.strip().splitlines():
|
||||
subprocess.run(
|
||||
['sudo', 'kill', '-9', pid.strip()],
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=5
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def disable_vless(observer=None) -> bool:
|
||||
revert_dns_on_tun()
|
||||
SingboxRunner().stop()
|
||||
killswitch.disarm()
|
||||
try:
|
||||
revert_dns_on_tun()
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
SingboxRunner().stop()
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
import subprocess, time
|
||||
time.sleep(0.5)
|
||||
result = subprocess.run(['pgrep', '-x', 'sing-box'], capture_output=True, text=True)
|
||||
if result.returncode == 0:
|
||||
_force_kill_singbox()
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
killswitch.disarm()
|
||||
except Exception:
|
||||
pass
|
||||
if observer:
|
||||
observer.notify("disconnected", {"tunnel_if": Constants.SINGBOX_TUN_IF})
|
||||
return True
|
||||
|
|
@ -24,8 +24,8 @@ class SingboxRunner:
|
|||
_START_POLL_S = 0.5
|
||||
_START_INITIAL_S = 2.0
|
||||
|
||||
_ORPHAN_GRACEFUL_TIMEOUT_S = 4.0
|
||||
_ORPHAN_FINAL_VERIFY_TIMEOUT_S = 2.0
|
||||
_ORPHAN_GRACEFUL_TIMEOUT_S = 2.0
|
||||
_ORPHAN_FINAL_VERIFY_TIMEOUT_S = 1.0
|
||||
_ORPHAN_POLL_S = 0.3
|
||||
|
||||
def __init__(self):
|
||||
|
|
@ -83,7 +83,7 @@ class SingboxRunner:
|
|||
stderr=subprocess.DEVNULL,
|
||||
stdin=subprocess.DEVNULL,
|
||||
env={**os.environ, 'SUDO_ASKPASS': '/bin/false'},
|
||||
timeout=15,
|
||||
timeout=5,
|
||||
)
|
||||
except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
|
||||
logger.warning("Graceful stop failed, attempting emergency kill")
|
||||
|
|
@ -190,7 +190,7 @@ class SingboxRunner:
|
|||
stderr=subprocess.DEVNULL,
|
||||
stdin=subprocess.DEVNULL,
|
||||
env={**os.environ, 'SUDO_ASKPASS': '/bin/false'},
|
||||
timeout=15,
|
||||
timeout=5,
|
||||
)
|
||||
except (OSError, subprocess.TimeoutExpired):
|
||||
pass
|
||||
|
|
|
|||
Loading…
Reference in a new issue