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.is_system_profile():
|
||||||
|
|
||||||
if profile.connection.needs_operator_proxy():
|
if profile.connection.code in ('vless', 'hysteria2'):
|
||||||
from core.controllers.ConnectionController import ConnectionController
|
ProfileController._enable_encrypted_proxy(profile, connection_observer=connection_observer)
|
||||||
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_observer is not None:
|
if profile_observer is not None:
|
||||||
profile_observer.notify('enabled', profile)
|
profile_observer.notify('enabled', profile)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# WireGuard and other system profiles — legacy flow
|
||||||
|
from core.controllers.ConnectionController import ConnectionController
|
||||||
try:
|
try:
|
||||||
ConnectionController.establish_connection(profile, ignore=ignore, connection_observer=connection_observer)
|
ConnectionController.establish_connection(profile, ignore=ignore, connection_observer=connection_observer)
|
||||||
except ConnectionError:
|
except ConnectionError:
|
||||||
|
|
@ -110,6 +93,37 @@ class ProfileController:
|
||||||
if profile_observer is not None:
|
if profile_observer is not None:
|
||||||
profile_observer.notify('enabled', profile)
|
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
|
@staticmethod
|
||||||
def run_monitor(profile: Union[SessionProfile, SystemProfile], monitor) -> None:
|
def run_monitor(profile: Union[SessionProfile, SystemProfile], monitor) -> None:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -65,8 +65,8 @@ class BaseProfile(ABC):
|
||||||
return type(self).__name__ == 'SystemProfile'
|
return type(self).__name__ == 'SystemProfile'
|
||||||
|
|
||||||
def save(self: Self):
|
def save(self: Self):
|
||||||
config_dict = self.to_dict() # Get dict, not JSON string
|
config_dict = self.to_dict()
|
||||||
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.
|
location_dict = self.location.to_dict() if self.location else None
|
||||||
|
|
||||||
config_dict["location"] = location_dict
|
config_dict["location"] = location_dict
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -209,7 +209,7 @@ class WebServiceApiService:
|
||||||
else:
|
else:
|
||||||
headers = None
|
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
|
@staticmethod
|
||||||
def __post(path, billing_code: Optional[str] = None, body: Optional[dict] = None, proxies: Optional[dict] = None):
|
def __post(path, billing_code: Optional[str] = None, body: Optional[dict] = None, proxies: Optional[dict] = None):
|
||||||
|
|
@ -221,7 +221,7 @@ class WebServiceApiService:
|
||||||
else:
|
else:
|
||||||
headers = None
|
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
|
@staticmethod
|
||||||
def get_cached_sync(proxies: Optional[dict] = None):
|
def get_cached_sync(proxies: Optional[dict] = None):
|
||||||
|
|
|
||||||
|
|
@ -134,10 +134,41 @@ def enable_hysteria(username: str, password: str, server_host: str,
|
||||||
_cleanup_all()
|
_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:
|
def disable_hysteria(observer=None) -> bool:
|
||||||
revert_dns_on_tun()
|
try:
|
||||||
SingboxRunner().stop()
|
revert_dns_on_tun()
|
||||||
killswitch.disarm()
|
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:
|
if observer:
|
||||||
observer.notify("disconnected", {"tunnel_if": Constants.SINGBOX_TUN_IF})
|
observer.notify("disconnected", {"tunnel_if": Constants.SINGBOX_TUN_IF})
|
||||||
return True
|
return True
|
||||||
|
|
@ -163,10 +163,41 @@ def enable_vless(vless_link: str, username: str, server_ip: str,
|
||||||
_cleanup_all()
|
_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:
|
def disable_vless(observer=None) -> bool:
|
||||||
revert_dns_on_tun()
|
try:
|
||||||
SingboxRunner().stop()
|
revert_dns_on_tun()
|
||||||
killswitch.disarm()
|
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:
|
if observer:
|
||||||
observer.notify("disconnected", {"tunnel_if": Constants.SINGBOX_TUN_IF})
|
observer.notify("disconnected", {"tunnel_if": Constants.SINGBOX_TUN_IF})
|
||||||
return True
|
return True
|
||||||
|
|
@ -24,8 +24,8 @@ class SingboxRunner:
|
||||||
_START_POLL_S = 0.5
|
_START_POLL_S = 0.5
|
||||||
_START_INITIAL_S = 2.0
|
_START_INITIAL_S = 2.0
|
||||||
|
|
||||||
_ORPHAN_GRACEFUL_TIMEOUT_S = 4.0
|
_ORPHAN_GRACEFUL_TIMEOUT_S = 2.0
|
||||||
_ORPHAN_FINAL_VERIFY_TIMEOUT_S = 2.0
|
_ORPHAN_FINAL_VERIFY_TIMEOUT_S = 1.0
|
||||||
_ORPHAN_POLL_S = 0.3
|
_ORPHAN_POLL_S = 0.3
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
@ -83,7 +83,7 @@ class SingboxRunner:
|
||||||
stderr=subprocess.DEVNULL,
|
stderr=subprocess.DEVNULL,
|
||||||
stdin=subprocess.DEVNULL,
|
stdin=subprocess.DEVNULL,
|
||||||
env={**os.environ, 'SUDO_ASKPASS': '/bin/false'},
|
env={**os.environ, 'SUDO_ASKPASS': '/bin/false'},
|
||||||
timeout=15,
|
timeout=5,
|
||||||
)
|
)
|
||||||
except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
|
except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
|
||||||
logger.warning("Graceful stop failed, attempting emergency kill")
|
logger.warning("Graceful stop failed, attempting emergency kill")
|
||||||
|
|
@ -190,7 +190,7 @@ class SingboxRunner:
|
||||||
stderr=subprocess.DEVNULL,
|
stderr=subprocess.DEVNULL,
|
||||||
stdin=subprocess.DEVNULL,
|
stdin=subprocess.DEVNULL,
|
||||||
env={**os.environ, 'SUDO_ASKPASS': '/bin/false'},
|
env={**os.environ, 'SUDO_ASKPASS': '/bin/false'},
|
||||||
timeout=15,
|
timeout=5,
|
||||||
)
|
)
|
||||||
except (OSError, subprocess.TimeoutExpired):
|
except (OSError, subprocess.TimeoutExpired):
|
||||||
pass
|
pass
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue