fix errors kill s.

This commit is contained in:
Zenaku 2026-06-09 09:07:44 -05:00
parent 445bd65b4a
commit b843160304
4 changed files with 244 additions and 20 deletions

View file

@ -1,4 +1,4 @@
from core.Errors import MissingSubscriptionError, InvalidSubscriptionError, ConnectionUnprotectedError, EndpointVerificationError, ProfileStateConflictError from core.Errors import MissingSubscriptionError, InvalidSubscriptionError, ConnectionUnprotectedError, EndpointVerificationError, ProfileStateConflictError, PaymentRequiredError
from core.controllers.ApplicationVersionController import ApplicationVersionController from core.controllers.ApplicationVersionController import ApplicationVersionController
from core.controllers.InvoiceController import InvoiceController from core.controllers.InvoiceController import InvoiceController
from core.controllers.LocationController import LocationController from core.controllers.LocationController import LocationController
@ -12,6 +12,7 @@ from core.models.system.SystemProfile import SystemProfile
from cli.helpers import sanitize_profile, parse_application_argument, parse_location_argument from cli.helpers import sanitize_profile, parse_application_argument, parse_location_argument
from cli.observers import profile_observer, application_version_observer, connection_observer, invoice_observer from cli.observers import profile_observer, application_version_observer, connection_observer, invoice_observer
from core.Errors import SingboxNotInstalledException from core.Errors import SingboxNotInstalledException
from cli.killswitch_monitor import KillswitchMonitor
import pprint import pprint
NAME = 'profile' NAME = 'profile'
@ -112,11 +113,82 @@ def handle(arguments, main_parser):
if profile is None: if profile is None:
main_parser.error('the following argument should be a valid reference: --id/-i') main_parser.error('the following argument should be a valid reference: --id/-i')
try: try:
ProfileController.enable(profile, ignore=ignore, pristine=arguments.pristine, asynchronous=True, profile_observer=profile_observer, application_version_observer=application_version_observer, connection_observer=connection_observer) from cli import observers as obs
except SingboxNotInstalledException as exception:
monitor = KillswitchMonitor()
timer_state = {'stop': None, 'reconnecting': None, '_retrying': False}
obs._timer_state = timer_state
def _stop_timer():
if timer_state['stop'] is not None:
timer_state['stop'].set()
timer_state['stop'] = None
def _do_disable():
_stop_timer()
ProfileController.disable(
profile,
ignore=ignore,
profile_observer=profile_observer,
connection_observer=connection_observer
)
def _on_leak():
_stop_timer()
print('\n')
print(' ⚠ Leak detected. Network blocked.')
print()
choice = None
while choice not in ('1', '2'):
print(' [1] Disable profile and restore internet')
print(' [2] Retry connection')
try:
choice = input('\n Choose an option: ').strip()
except KeyboardInterrupt:
print()
_do_disable()
return
if choice == '1':
_do_disable()
elif choice == '2':
try:
obs._timer_state['_retrying'] = True
ProfileController.enable(
profile, ignore=ignore, pristine=False,
asynchronous=True, profile_observer=profile_observer,
application_version_observer=application_version_observer,
connection_observer=connection_observer
)
monitor.run_blocking()
except KeyboardInterrupt:
print()
_do_disable()
monitor.set_on_disconnect(_do_disable)
monitor.set_on_leak(_on_leak)
obs._monitor = monitor
try:
ProfileController.enable(
profile, ignore=ignore, pristine=arguments.pristine,
asynchronous=True, profile_observer=profile_observer,
application_version_observer=application_version_observer,
connection_observer=connection_observer
)
monitor.run_blocking()
_stop_timer()
except KeyboardInterrupt:
print()
_do_disable()
except SingboxNotInstalledException:
main_parser.error('sing-box is not installed. Please run the installation script first.') main_parser.error('sing-box is not installed. Please run the installation script first.')
except PaymentRequiredError:
print('Payment processing is currently unavailable. The subscription could not be activated.\nPlease try again later or contact support.')
except (InvalidSubscriptionError, MissingSubscriptionError) as exception: except (InvalidSubscriptionError, MissingSubscriptionError) as exception:
_handle_subscription_error(exception, profile, ignore, arguments, main_parser) _handle_subscription_error(exception, profile, ignore, arguments, main_parser)
elif arguments.subcommand == 'disable': elif arguments.subcommand == 'disable':
profile = ProfileController.get(arguments.id) profile = ProfileController.get(arguments.id)
if profile is not None: if profile is not None:
@ -124,6 +196,7 @@ def handle(arguments, main_parser):
else: else:
main_parser.error('the following argument should be a valid reference: --id/-i') main_parser.error('the following argument should be a valid reference: --id/-i')
def _handle_subscription_error(exception, profile, ignore, arguments, main_parser): def _handle_subscription_error(exception, profile, ignore, arguments, main_parser):
if type(exception).__name__ == 'InvalidSubscriptionError': if type(exception).__name__ == 'InvalidSubscriptionError':
print('The profile\'s subscription appears to be invalid.\n') print('The profile\'s subscription appears to be invalid.\n')
@ -150,17 +223,21 @@ def _handle_subscription_error(exception, profile, ignore, arguments, main_parse
potential_subscription = SubscriptionController.create(subscription_plan, profile, connection_observer=connection_observer) potential_subscription = SubscriptionController.create(subscription_plan, profile, connection_observer=connection_observer)
if potential_subscription is None: if potential_subscription is None:
raise RuntimeError('The subscription could not be created. Please try again later.') raise RuntimeError('The subscription could not be created. Please try again later.')
ProfileController.attach_subscription(profile, potential_subscription) ProfileController.attach_subscription(profile, potential_subscription)
# Operator no necesita invoice
if potential_subscription.operator_id is None: if potential_subscription.operator_id is None:
subscription = InvoiceController.handle_payment(potential_subscription.billing_code, invoice_observer=invoice_observer, connection_observer=connection_observer) subscription = InvoiceController.handle_payment(potential_subscription.billing_code, invoice_observer=invoice_observer, connection_observer=connection_observer)
if subscription is None: if subscription is None:
raise RuntimeError('The subscription could not be activated. Please try again later.') print('\nPayment processing is currently unavailable. Please try again later or contact support.')
return
ProfileController.attach_subscription(profile, subscription) ProfileController.attach_subscription(profile, subscription)
ProfileController.enable(profile, ignore=ignore, pristine=arguments.pristine, asynchronous=True, profile_observer=profile_observer, application_version_observer=application_version_observer, connection_observer=connection_observer) try:
ProfileController.enable(profile, ignore=ignore, pristine=arguments.pristine, asynchronous=True, profile_observer=profile_observer, application_version_observer=application_version_observer, connection_observer=connection_observer)
except PaymentRequiredError:
print('\nPayment processing is currently unavailable. The subscription could not be activated.\nPlease try again later or contact support.')
return
elif manage_subscription_input == '2': elif manage_subscription_input == '2':
billing_code = input('\nEnter your billing code: ') billing_code = input('\nEnter your billing code: ')
@ -168,7 +245,11 @@ def _handle_subscription_error(exception, profile, ignore, arguments, main_parse
subscription = SubscriptionController.get(billing_code, connection_observer=connection_observer) subscription = SubscriptionController.get(billing_code, connection_observer=connection_observer)
if subscription is not None: if subscription is not None:
ProfileController.attach_subscription(profile, subscription) ProfileController.attach_subscription(profile, subscription)
ProfileController.enable(profile, ignore=ignore, pristine=arguments.pristine, asynchronous=True, profile_observer=profile_observer, application_version_observer=application_version_observer, connection_observer=connection_observer) try:
ProfileController.enable(profile, ignore=ignore, pristine=arguments.pristine, asynchronous=True, profile_observer=profile_observer, application_version_observer=application_version_observer, connection_observer=connection_observer)
except PaymentRequiredError:
print('\nPayment processing is currently unavailable. The subscription could not be activated.\nPlease try again later or contact support.')
return
else: else:
print('\nThe billing code appears to be invalid.\n') print('\nThe billing code appears to be invalid.\n')
manage_subscription_input = None manage_subscription_input = None
@ -178,6 +259,7 @@ def _handle_subscription_error(exception, profile, ignore, arguments, main_parse
else: else:
print('\nInput appears to be invalid. Please try again.\n') print('\nInput appears to be invalid. Please try again.\n')
def _build_ignore(arguments): def _build_ignore(arguments):
ignore = [] ignore = []
if getattr(arguments, 'without_connection_protection', False): if getattr(arguments, 'without_connection_protection', False):

54
cli/killswitch_monitor.py Normal file
View file

@ -0,0 +1,54 @@
import time
import threading
from core.utils.encrypted_proxy import killswitch
from core.utils.encrypted_proxy.net import get_proxied_ip
class KillswitchMonitor:
POLL_INTERVAL = 10
def __init__(self):
self._on_disconnect = None
self._on_leak = None
self._socks5_port = None
self._real_ip = None
self._running = False
self._stop_event = threading.Event()
def set_on_disconnect(self, fn):
self._on_disconnect = fn
def set_on_leak(self, fn):
self._on_leak = fn
def set_connection_data(self, socks5_port, real_ip):
self._socks5_port = socks5_port
self._real_ip = real_ip
def stop(self):
self._running = False
self._stop_event.set()
def run_blocking(self):
self._running = True
self._stop_event.clear()
while self._running:
interrupted = self._stop_event.wait(timeout=self.POLL_INTERVAL)
if interrupted or not self._running:
break
if not killswitch.status():
if self._on_disconnect:
self._on_disconnect()
self._running = False
return
if self._socks5_port and self._real_ip:
proxied = get_proxied_ip(self._socks5_port, timeout=10)
if proxied == "unknown" or proxied == self._real_ip:
if self._on_leak:
self._on_leak()
self._running = False
return

View file

@ -1,10 +1,12 @@
from core.utils.encrypted_proxy.net import get_proxied_ip
from core.utils.encrypted_proxy import killswitch
from core.controllers.ApplicationController import ApplicationController from core.controllers.ApplicationController import ApplicationController
from core.observers.ApplicationVersionObserver import ApplicationVersionObserver from core.observers.ApplicationVersionObserver import ApplicationVersionObserver
from core.observers.ClientObserver import ClientObserver from core.observers.ClientObserver import ClientObserver
from core.observers.ConnectionObserver import ConnectionObserver from core.observers.ConnectionObserver import ConnectionObserver
from core.observers.InvoiceObserver import InvoiceObserver from core.observers.InvoiceObserver import InvoiceObserver
from core.observers.ProfileObserver import ProfileObserver from core.observers.ProfileObserver import ProfileObserver
from cli.ui import Spinner, label_ok, label_error, label_warn, label_connected, label_disconnect, progress_bar from cli.ui import Spinner, label_ok, label_error, label_warn, label_connected, label_disconnect, progress_bar, label_killswitch
from cli.helpers import sanitize_profile from cli.helpers import sanitize_profile
import pprint import pprint
@ -14,6 +16,10 @@ connection_observer = ConnectionObserver()
invoice_observer = InvoiceObserver() invoice_observer = InvoiceObserver()
profile_observer = ProfileObserver() profile_observer = ProfileObserver()
_spinner = None
_monitor = None
_timer_state = None
application_version_observer.subscribe('downloading', lambda event: print(f'Downloading {ApplicationController.get(event.subject.application_code).name}, version {event.subject.version_number}...')) application_version_observer.subscribe('downloading', lambda event: print(f'Downloading {ApplicationController.get(event.subject.application_code).name}, version {event.subject.version_number}...'))
application_version_observer.subscribe('download_progressing', lambda event: print(f'Current progress: {event.meta.get("progress"):.2f}%', flush=True, end='\r')) application_version_observer.subscribe('download_progressing', lambda event: print(f'Current progress: {event.meta.get("progress"):.2f}%', flush=True, end='\r'))
application_version_observer.subscribe('downloaded', lambda event: print('\n')) application_version_observer.subscribe('downloaded', lambda event: print('\n'))
@ -23,48 +29,84 @@ client_observer.subscribe('updating', lambda event: print('Updating client...'))
client_observer.subscribe('update_progressing', lambda event: print(f'Current progress: {event.meta.get("progress"):.2f}%', flush=True, end='\r')) client_observer.subscribe('update_progressing', lambda event: print(f'Current progress: {event.meta.get("progress"):.2f}%', flush=True, end='\r'))
client_observer.subscribe('updated', lambda event: print('\n')) client_observer.subscribe('updated', lambda event: print('\n'))
_spinner = None
def _on_connecting(event): def _on_connecting(event):
global _spinner global _spinner, _timer_state
if _timer_state and _timer_state['stop']:
_timer_state['stop'].set()
_timer_state['stop'] = None
if _timer_state and _timer_state['reconnecting'] is None and _timer_state.get('_retrying'):
from cli.ui import connecting_spinner
_timer_state['reconnecting'] = connecting_spinner()
attempt = event.subject.get("attempt_count", "?") attempt = event.subject.get("attempt_count", "?")
total = event.subject.get("maximum_number_of_attempts", "?") total = event.subject.get("maximum_number_of_attempts", "?")
_spinner = Spinner(f"Connecting... [{attempt}/{total}]") _spinner = Spinner(f"[net] verify_ip attempt {attempt}/{total}...")
_spinner.start() _spinner.start()
def _on_connected(event): def _on_connected(event):
global _spinner global _spinner, _monitor, _timer_state
if _spinner: if _spinner:
_spinner.stop() _spinner.stop()
_spinner = None _spinner = None
if _timer_state and _timer_state.get('reconnecting'):
_timer_state['reconnecting'].set()
_timer_state['reconnecting'] = None
if _timer_state:
_timer_state['_retrying'] = False
d = event.subject or {} d = event.subject or {}
label_connected(f"proxy={d.get('proxy_ip','?')} real={d.get('real_ip','?')} port={d.get('socks5_port','?')}") label_connected(f"proxy={d.get('proxy_ip','?')} real={d.get('real_ip','?')} port={d.get('socks5_port','?')}")
label_killswitch(killswitch.status())
if _monitor is not None:
_monitor.set_connection_data(
socks5_port=d.get('socks5_port'),
real_ip=d.get('real_ip')
)
if _timer_state is not None:
from cli.ui import session_timer
_timer_state['stop'] = session_timer()
def _on_disconnected(event): def _on_disconnected(event):
global _spinner global _spinner, _monitor, _timer_state
if _spinner: if _spinner:
_spinner.stop() _spinner.stop()
_spinner = None _spinner = None
if _timer_state and _timer_state['stop']:
_timer_state['stop'].set()
_timer_state['stop'] = None
if _monitor is not None:
_monitor.stop()
_monitor = None
d = event.subject or {} d = event.subject or {}
ip = d.get("ip", "?") label_disconnect(f"real={d.get('ip','?')}")
label_disconnect(f"real={ip}")
def _on_error(event): def _on_error(event):
global _spinner global _spinner, _monitor, _timer_state
if _spinner: if _spinner:
_spinner.stop() _spinner.stop()
_spinner = None _spinner = None
if _timer_state and _timer_state['stop']:
_timer_state['stop'].set()
_timer_state['stop'] = None
if _monitor is not None:
_monitor.stop()
_monitor = None
label_error(str(event.subject or "Unknown error")) label_error(str(event.subject or "Unknown error"))
def _on_tor_bootstrapping(event): def _on_tor_bootstrapping(event):
global _spinner global _spinner
_spinner = Spinner("Starting Tor...") _spinner = Spinner("Starting Tor...")
_spinner.start() _spinner.start()
def _on_tor_bootstrap_progressing(event): def _on_tor_bootstrap_progressing(event):
pct = int(event.meta.get("progress", 0)) pct = int(event.meta.get("progress", 0))
progress_bar(pct, 100, "Tor bootstrap") progress_bar(pct, 100, "Tor bootstrap")
def _on_tor_bootstrapped(event): def _on_tor_bootstrapped(event):
global _spinner global _spinner
if _spinner: if _spinner:
@ -73,6 +115,7 @@ def _on_tor_bootstrapped(event):
print() print()
label_ok("Tor ready") label_ok("Tor ready")
connection_observer.subscribe('connecting', _on_connecting) connection_observer.subscribe('connecting', _on_connecting)
connection_observer.subscribe('connected', _on_connected) connection_observer.subscribe('connected', _on_connected)
connection_observer.subscribe('disconnected', _on_disconnected) connection_observer.subscribe('disconnected', _on_disconnected)

View file

@ -141,4 +141,49 @@ def wait_with_spinner(msg: str, fn, *args, result_msg: str = "", **kwargs):
if result_box[1] is not None: if result_box[1] is not None:
raise result_box[1] raise result_box[1]
return result_box[0] return result_box[0]
def label_killswitch(armed: bool):
if armed:
print(f" {BGREEN}[ KS ]{RESET} Kill switch active")
else:
print(f" {BRED}[ KS ]{RESET} Kill switch {BRED}INACTIVE — connection unprotected{RESET}")
def session_timer() -> threading.Event:
stop_event = threading.Event()
start = time.time()
def _run():
while not stop_event.is_set():
elapsed = int(time.time() - start)
h = elapsed // 3600
m = (elapsed % 3600) // 60
s = elapsed % 60
sys.stdout.write(
f"\r {BGREEN}{RESET} {BWHITE}Protected{RESET} "
f"{DIM}[{h:02d}:{m:02d}:{s:02d}]{RESET} "
)
sys.stdout.flush()
time.sleep(1)
sys.stdout.write("\r" + " " * 50 + "\r")
sys.stdout.flush()
threading.Thread(target=_run, daemon=True).start()
return stop_event
def connecting_spinner() -> threading.Event:
stop_event = threading.Event()
frames = ("", "\\", "", "/")
def _run():
i = 0
while not stop_event.is_set():
frame = frames[i % len(frames)]
sys.stdout.write(
f"\r {BYELLOW}{frame}{RESET} {DIM}Reconnecting...{RESET} "
)
sys.stdout.flush()
i += 1
time.sleep(0.12)
sys.stdout.write("\r" + " " * 60 + "\r")
sys.stdout.flush()
threading.Thread(target=_run, daemon=True).start()
return stop_event