Plumbing switched to regular thread (not executor). Introduced Port checking and changing as part of wireguard failure
This commit is contained in:
parent
d7f953b907
commit
25d69d728c
12 changed files with 290 additions and 89 deletions
|
|
@ -177,3 +177,21 @@ class ConfigurationController:
|
||||||
configuration = ConfigurationController.get()
|
configuration = ConfigurationController.get()
|
||||||
return configuration.tickets_setup
|
return configuration.tickets_setup
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def do_we_allow_port_prompts() -> bool:
|
||||||
|
configuration = ConfigurationController.get()
|
||||||
|
|
||||||
|
if configuration.allow_port_prompts is None:
|
||||||
|
print("Setting allow ports to true")
|
||||||
|
configuration.allow_port_prompts = True
|
||||||
|
configuration.save()
|
||||||
|
|
||||||
|
return configuration.allow_port_prompts
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def change_port_prompts(new_value) -> bool:
|
||||||
|
configuration = ConfigurationController.get()
|
||||||
|
configuration.allow_port_prompts = new_value
|
||||||
|
configuration.save()
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ class GlobalState:
|
||||||
systemwide_profile_id: Optional[int] = None
|
systemwide_profile_id: Optional[int] = None
|
||||||
session_profile_ids: list[int] = field(default_factory=list)
|
session_profile_ids: list[int] = field(default_factory=list)
|
||||||
verified: bool = False
|
verified: bool = False
|
||||||
|
changed_port: bool = False
|
||||||
|
|
||||||
class ProfileStateRepository:
|
class ProfileStateRepository:
|
||||||
_instance = None
|
_instance = None
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ from functools import partial
|
||||||
|
|
||||||
SINGBOX_PROTOCOLS = (ProtocolChoice.HYSTERIA2, ProtocolChoice.VLESS)
|
SINGBOX_PROTOCOLS = (ProtocolChoice.HYSTERIA2, ProtocolChoice.VLESS)
|
||||||
|
|
||||||
|
|
||||||
def verify_systemwide_state(current_state: SystemState) -> bool:
|
def verify_systemwide_state(current_state: SystemState) -> bool:
|
||||||
"""Confirm a valid connection, to update GlobalState (for UI)"""
|
"""Confirm a valid connection, to update GlobalState (for UI)"""
|
||||||
state_repo = ProfileStateRepository()
|
state_repo = ProfileStateRepository()
|
||||||
|
|
|
||||||
|
|
@ -2,19 +2,17 @@ from core.infrastructure.thread_dispatch import MainThreadDispatcher
|
||||||
from core.infrastructure.error_popup import PopupProtocol, StackErrorProtocol
|
from core.infrastructure.error_popup import PopupProtocol, StackErrorProtocol
|
||||||
from core.infrastructure.service_registry import ServiceLocator
|
from core.infrastructure.service_registry import ServiceLocator
|
||||||
from core.errors.translate_error import get_traceback_line
|
from core.errors.translate_error import get_traceback_line
|
||||||
from core.errors.ErrorStack import ErrorStack
|
|
||||||
from core.models.Result import Result, ResultError
|
from core.models.Result import Result, ResultError
|
||||||
from core.errors.logger import logger
|
from core.errors.logger import logger
|
||||||
|
|
||||||
# generic:
|
# generic:
|
||||||
from concurrent.futures import ThreadPoolExecutor, Future
|
import threading
|
||||||
from typing import Callable, Any, Optional, Type
|
from typing import Callable, Any, Optional, Type
|
||||||
import inspect
|
import inspect
|
||||||
from functools import partial
|
from functools import partial
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
MAX_ATTEMPTS = 2
|
MAX_ATTEMPTS = 2
|
||||||
executor = ThreadPoolExecutor(max_workers=1)
|
|
||||||
|
|
||||||
class ThreadPlumbing:
|
class ThreadPlumbing:
|
||||||
"""
|
"""
|
||||||
|
|
@ -34,35 +32,19 @@ class ThreadPlumbing:
|
||||||
self.kwargs = kwargs
|
self.kwargs = kwargs
|
||||||
self.attempt_count = 0
|
self.attempt_count = 0
|
||||||
|
|
||||||
def execute(self):
|
def execute(self, task = None):
|
||||||
"""Entry point: kick off the first attempt"""
|
"""Kicks off the action in a background thread"""
|
||||||
self.attempt_count += 1
|
|
||||||
self._spawn_action(action=self.action, first_time=True)
|
|
||||||
|
|
||||||
def _spawn_action(self, action: Callable, first_time: bool = None):
|
# 0. If there's no task passed in, get it from the object initalization,
|
||||||
"""Spawn action on executor"""
|
if not task:
|
||||||
if first_time:
|
# 1. Create the partial immediately in the main thread
|
||||||
future = executor.submit(action, *self.args, **self.kwargs) # original args/kwargs
|
task = partial(self.action, *self.args, **self.kwargs)
|
||||||
else:
|
|
||||||
future = executor.submit(action) # this is a pre-loaded partial
|
|
||||||
future.add_done_callback(self._on_complete)
|
|
||||||
|
|
||||||
def _on_complete(self, future: Future):
|
# 2. Define a simple wrapper to run the task and then handle the result
|
||||||
"""Handle thread completion"""
|
def wrapper():
|
||||||
try:
|
try:
|
||||||
logger.info("The plumbing module started it's 'on complete' phase with this result:")
|
result = task() # Run the partial
|
||||||
result = future.result()
|
logger.info(f"post complete result here is {result}")
|
||||||
if not isinstance(result, Result):
|
|
||||||
result = convert_to_result(result)
|
|
||||||
|
|
||||||
logger.info(result)
|
|
||||||
if not result.valid:
|
|
||||||
if not result.function_choice:
|
|
||||||
result.function_choice = resolve_prescription(result, self.ENUM_MAP, *self.args, **self.kwargs)
|
|
||||||
logger.info(f"result.function_choice is {result.function_choice}")
|
|
||||||
else:
|
|
||||||
logger.info("Using the pre-existing function choice for this error.")
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error_msg = f"There was an error: {str(e)}"
|
error_msg = f"There was an error: {str(e)}"
|
||||||
logger.error(error_msg)
|
logger.error(error_msg)
|
||||||
|
|
@ -70,13 +52,34 @@ class ThreadPlumbing:
|
||||||
error_msg = error_stack.message_for_user
|
error_msg = error_stack.message_for_user
|
||||||
logger.error(error_msg)
|
logger.error(error_msg)
|
||||||
result = Result(valid=False, error_stack=error_stack, error_type=ResultError.UNKNOWN, message=error_msg)
|
result = Result(valid=False, error_stack=error_stack, error_type=ResultError.UNKNOWN, message=error_msg)
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
# Back to main thread
|
self._on_complete(result) # Process the result
|
||||||
print(f"result in plumbing is {result}")
|
|
||||||
self.ui_dispatcher.run_on_main_thread(self._on_result_ready, result)
|
|
||||||
|
|
||||||
def _on_result_ready(self, result: Result):
|
# 3. Spin up a single thread and start it
|
||||||
|
thread = threading.Thread(target=wrapper, daemon=True)
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
def _on_complete(self, result: Result):
|
||||||
|
"""Get a potential function prescription and return it to the main thread."""
|
||||||
|
|
||||||
|
#----------------------------
|
||||||
|
# Function Prescription:
|
||||||
|
#----------------------------
|
||||||
|
if not result.valid:
|
||||||
|
if not result.function_choice:
|
||||||
|
result.function_choice = resolve_prescription(result, self.ENUM_MAP, *self.args, **self.kwargs)
|
||||||
|
logger.info(f"result.function_choice is {result.function_choice}")
|
||||||
|
else:
|
||||||
|
logger.info("Using the pre-existing function choice for this error.")
|
||||||
|
|
||||||
|
#----------------------------
|
||||||
|
# Back to main thread
|
||||||
|
#----------------------------
|
||||||
|
# logger.info(f"result in plumbing background thread is {result}")
|
||||||
|
self.ui_dispatcher.run_on_main_thread(self._back_on_main_thread, result)
|
||||||
|
# print("ran on main thread")
|
||||||
|
|
||||||
|
def _back_on_main_thread(self, result: Result):
|
||||||
"""Decide: retry, show popup, or final callback"""
|
"""Decide: retry, show popup, or final callback"""
|
||||||
|
|
||||||
logger.info(f"We are on the main thread with result {result}")
|
logger.info(f"We are on the main thread with result {result}")
|
||||||
|
|
@ -92,6 +95,14 @@ class ThreadPlumbing:
|
||||||
sys.exit()
|
sys.exit()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
#----------------------------
|
||||||
|
# NAVIGATE AWAY
|
||||||
|
#----------------------------
|
||||||
|
# If the result sender explicitly asked us to navigate to a page with a ui callback, then bypass the popup:
|
||||||
|
if result.error_type == ResultError.NAVIGATE_TO and self.ui_caller:
|
||||||
|
logger.info("Plumbing was asked to NAVIGATE_TO so skipping to the ui_caller..")
|
||||||
|
self.ui_caller(result)
|
||||||
|
|
||||||
#----------------------------
|
#----------------------------
|
||||||
# MAX TRIES
|
# MAX TRIES
|
||||||
#----------------------------
|
#----------------------------
|
||||||
|
|
@ -103,16 +114,9 @@ class ThreadPlumbing:
|
||||||
self.ui_caller(result)
|
self.ui_caller(result)
|
||||||
return
|
return
|
||||||
|
|
||||||
# does this have an auto-navigate page?
|
#----------------------------
|
||||||
# if self.navigator and self.auto_navigate_dict:
|
# SHOW CHOICE & POPUP
|
||||||
# logger.info("checking auto-navigate..")
|
#----------------------------
|
||||||
# self.check_auto_navigation(result)
|
|
||||||
|
|
||||||
# If the result sender explicitly asked us to navigate to a page with a ui callback, then bypass the popup:
|
|
||||||
if result.error_type == ResultError.NAVIGATE_TO and self.ui_caller:
|
|
||||||
logger.info("Plumbing was asked to NAVIGATE_TO so skipping to the ui_caller..")
|
|
||||||
self.ui_caller(result)
|
|
||||||
|
|
||||||
# do we need to decide on an action?
|
# do we need to decide on an action?
|
||||||
choice = bool(result.function_choice)
|
choice = bool(result.function_choice)
|
||||||
logger.info(f"Do we have to make a choice? {choice}")
|
logger.info(f"Do we have to make a choice? {choice}")
|
||||||
|
|
@ -133,6 +137,9 @@ class ThreadPlumbing:
|
||||||
popup.show()
|
popup.show()
|
||||||
logger.info("Showed the error popup.")
|
logger.info("Showed the error popup.")
|
||||||
|
|
||||||
|
#----------------------------
|
||||||
|
# COMPLETE
|
||||||
|
#----------------------------
|
||||||
# otherwise return it as is:
|
# otherwise return it as is:
|
||||||
if self.ui_caller:
|
if self.ui_caller:
|
||||||
logger.info("Returning from plumbing..")
|
logger.info("Returning from plumbing..")
|
||||||
|
|
@ -141,26 +148,34 @@ class ThreadPlumbing:
|
||||||
|
|
||||||
def _on_popup_answer(self, user_choice: bool, passed_result: Result):
|
def _on_popup_answer(self, user_choice: bool, passed_result: Result):
|
||||||
"""User responded to popup"""
|
"""User responded to popup"""
|
||||||
|
|
||||||
|
#---------------------
|
||||||
|
# RUN THE CHOICE:
|
||||||
|
#---------------------
|
||||||
if user_choice and passed_result.function_choice:
|
if user_choice and passed_result.function_choice:
|
||||||
self.attempt_count += 1
|
self.attempt_count += 1
|
||||||
self._spawn_action(passed_result.function_choice)
|
self.execute(passed_result.function_choice)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
#---------------------
|
||||||
|
# OR RUN THE "NO" CHOICE:
|
||||||
|
#---------------------
|
||||||
# if the result originally prescribed it's own "no option":
|
# if the result originally prescribed it's own "no option":
|
||||||
no_option = passed_result.no_option
|
no_option = passed_result.no_option
|
||||||
if no_option:
|
if no_option:
|
||||||
no_option()
|
no_option()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
#---------------------
|
||||||
|
# OR Just return back,
|
||||||
|
#---------------------
|
||||||
# otherwise do the original UI call:
|
# otherwise do the original UI call:
|
||||||
if self.ui_caller:
|
if self.ui_caller:
|
||||||
self.ui_caller(passed_result)
|
self.ui_caller(passed_result)
|
||||||
|
|
||||||
def check_auto_navigation(self, result: Result):
|
|
||||||
auto_navigate_page = self.auto_navigate_dict.get(result.error_type)
|
|
||||||
if auto_navigate_page is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
self.navigator.request_navigation.emit(auto_navigate_page)
|
|
||||||
|
# STANDALONE HELPERS:
|
||||||
|
|
||||||
|
|
||||||
def normalize_seq(s):
|
def normalize_seq(s):
|
||||||
|
|
@ -302,3 +317,44 @@ def convert_to_result(result: Any) -> Result:
|
||||||
def take_plumbing_action(action: Callable, ui_dispatcher: MainThreadDispatcher, error_implementation: Type[PopupProtocol], ui_caller: Callable, *args, **kwargs):
|
def take_plumbing_action(action: Callable, ui_dispatcher: MainThreadDispatcher, error_implementation: Type[PopupProtocol], ui_caller: Callable, *args, **kwargs):
|
||||||
controller = ThreadPlumbing(action, ui_dispatcher, ui_caller, *args, **kwargs)
|
controller = ThreadPlumbing(action, ui_dispatcher, ui_caller, *args, **kwargs)
|
||||||
controller.execute()
|
controller.execute()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# def execute(self):
|
||||||
|
# """Entry point: kick off the first attempt"""
|
||||||
|
# self.attempt_count += 1
|
||||||
|
# self._spawn_action(action=self.action, first_time=True)
|
||||||
|
|
||||||
|
# def _spawn_action(self, action: Callable, first_time: bool = None):
|
||||||
|
# """Spawn action on executor"""
|
||||||
|
# if first_time:
|
||||||
|
# future = executor.submit(action, *self.args, **self.kwargs) # original args/kwargs
|
||||||
|
# else:
|
||||||
|
# future = executor.submit(action) # this is a pre-loaded partial
|
||||||
|
# future.add_done_callback(self._on_complete)
|
||||||
|
|
||||||
|
|
||||||
|
# result = future.result()
|
||||||
|
# if not isinstance(result, Result):
|
||||||
|
# result = convert_to_result(result)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Auto navigate:
|
||||||
|
|
||||||
|
# does this have an auto-navigate page?
|
||||||
|
# if self.navigator and self.auto_navigate_dict:
|
||||||
|
# logger.info("checking auto-navigate..")
|
||||||
|
# self.check_auto_navigation(result)
|
||||||
|
|
||||||
|
|
||||||
|
# def check_auto_navigation(self, result: Result):
|
||||||
|
# auto_navigate_page = self.auto_navigate_dict.get(result.error_type)
|
||||||
|
# if auto_navigate_page is None:
|
||||||
|
# return
|
||||||
|
|
||||||
|
# self.navigator.request_navigation.emit(auto_navigate_page)
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ class Configuration(BaseModel):
|
||||||
singbox: Optional[str] = None
|
singbox: Optional[str] = None
|
||||||
tickets_setup: Optional[bool] = False
|
tickets_setup: Optional[bool] = False
|
||||||
use_random: Optional[bool] = False
|
use_random: Optional[bool] = False
|
||||||
|
allow_port_prompts: Optional[bool] = True
|
||||||
|
|
||||||
|
|
||||||
@field_validator('last_synced_at', mode='before')
|
@field_validator('last_synced_at', mode='before')
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,7 @@ class ResultError(Enum):
|
||||||
API_DOWN = "api_down"
|
API_DOWN = "api_down"
|
||||||
NODE_ERROR = "node_error"
|
NODE_ERROR = "node_error"
|
||||||
NO_INTERNET = "no_internet"
|
NO_INTERNET = "no_internet"
|
||||||
|
PORT = "port"
|
||||||
NAVIGATE_TO = "navigate_to"
|
NAVIGATE_TO = "navigate_to"
|
||||||
UNKNOWN = "unknown"
|
UNKNOWN = "unknown"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
from core.controllers.ConfigurationController import ConfigurationController
|
||||||
|
from core.infrastructure.global_state import ProfileStateRepository
|
||||||
from core.utils.get_raw_string import get_string
|
from core.utils.get_raw_string import get_string
|
||||||
from core.models.manage.server_reply_tools import api_call_and_extraction, ApiCall
|
from core.models.manage.server_reply_tools import api_call_and_extraction, ApiCall
|
||||||
# from core.services.networking.httpx import connect
|
# from core.services.networking.httpx import connect
|
||||||
|
|
@ -17,13 +19,18 @@ from core.errors.logger import logger
|
||||||
#from core.models.manage.server_reply_tools import format_server_response
|
#from core.models.manage.server_reply_tools import format_server_response
|
||||||
# from core.models.pydantic_models.ServerReply import ServerReply, ServerError
|
# from core.models.pydantic_models.ServerReply import ServerReply, ServerError
|
||||||
from core.models.Result import Result, ResultError
|
from core.models.Result import Result, ResultError
|
||||||
|
from core.Errors import ProfileModificationError, CommandNotFoundError
|
||||||
|
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
import base64
|
import base64
|
||||||
import re
|
import re
|
||||||
|
import random
|
||||||
|
from functools import partial
|
||||||
|
|
||||||
MIN_MTU = 1280
|
MIN_MTU = 1280
|
||||||
|
|
||||||
|
PORT_CHOICES = [48271, 52428, 51820]
|
||||||
|
|
||||||
def register_wireguard_session(
|
def register_wireguard_session(
|
||||||
profile: Profile,
|
profile: Profile,
|
||||||
connection_observer: Optional[ConnectionObserver] = None
|
connection_observer: Optional[ConnectionObserver] = None
|
||||||
|
|
@ -181,3 +188,113 @@ def expand_newlines(text):
|
||||||
empty line between every non-empty line of text.
|
empty line between every non-empty line of text.
|
||||||
"""
|
"""
|
||||||
return "\n\n".join([line for line in text.splitlines() if line.strip()])
|
return "\n\n".join([line for line in text.splitlines() if line.strip()])
|
||||||
|
|
||||||
|
|
||||||
|
def get_port(config: str) -> int:
|
||||||
|
"""Get the wireguard port if you have a config already."""
|
||||||
|
# This regex searches for the "Endpoint" key, ignores optional whitespace around the '=',
|
||||||
|
# skips the IP/hostname part until the colon, and captures the digits following it.
|
||||||
|
match = re.search(r"Endpoint\s*=\s*[^:]+:(\d+)", config, re.IGNORECASE)
|
||||||
|
if match:
|
||||||
|
return int(match.group(1))
|
||||||
|
return None
|
||||||
|
|
||||||
|
def change_port(new_port: int, config: str) -> str:
|
||||||
|
# This regex captures everything from the start of the line up to the colon
|
||||||
|
# in the first group, then matches the existing digits of the port.
|
||||||
|
pattern = r"(Endpoint\s*=\s*[^:]+):(\d+)"
|
||||||
|
|
||||||
|
# We use a lambda function as the replacement to insert the new_port variable
|
||||||
|
# while preserving the captured IP/hostname in group 1 (\1).
|
||||||
|
return re.sub(pattern, lambda m: f"{m.group(1)}:{new_port}", config, flags=re.IGNORECASE)
|
||||||
|
|
||||||
|
|
||||||
|
def get_wg_port(profile: Profile) -> Result:
|
||||||
|
"""Get the Wireguard port with a given profile."""
|
||||||
|
config_path = profile.get_wireguard_configuration_path()
|
||||||
|
if not config_path:
|
||||||
|
return Result(valid=False, error_type=ResultError.INVALID_INPUT, message="Can't get the path of the config")
|
||||||
|
|
||||||
|
config_data = get_string(config_path)
|
||||||
|
if not config_data:
|
||||||
|
return Result(valid=False, error_type=ResultError.MISSING_DATA, message="Missing the config data, or could not read it.")
|
||||||
|
|
||||||
|
port = get_port(config_data)
|
||||||
|
if port:
|
||||||
|
return Result(valid=True, data=port)
|
||||||
|
else:
|
||||||
|
return Result(valid=False, error_type=ResultError.INVALID_INPUT, message="Config is not in the correct format to be read.")
|
||||||
|
|
||||||
|
|
||||||
|
def get_random_different_port(current_port: int) -> int|None:
|
||||||
|
"""Get a different port (that's available) from the given choices."""
|
||||||
|
while True:
|
||||||
|
new_port = random.choice(PORT_CHOICES)
|
||||||
|
if str(new_port) != str(current_port):
|
||||||
|
return new_port
|
||||||
|
|
||||||
|
|
||||||
|
def change_wg_port(profile: Profile = None, desired_port: Optional[int] = None) -> Result:
|
||||||
|
"""Change a profile's Wireguard port."""
|
||||||
|
|
||||||
|
logger.info(f"desired_port is {desired_port}")
|
||||||
|
config_path = profile.get_wireguard_configuration_path()
|
||||||
|
config_data = get_string(config_path)
|
||||||
|
current_port = get_port(config_data)
|
||||||
|
logger.info(f"current_port is {current_port}")
|
||||||
|
|
||||||
|
if desired_port and desired_port != current_port and desired_port in PORT_CHOICES:
|
||||||
|
change_to = desired_port
|
||||||
|
else:
|
||||||
|
change_to = get_random_different_port(current_port)
|
||||||
|
logger.info(f"Changing the port to {change_to}")
|
||||||
|
|
||||||
|
new_config = change_port(new_port=change_to, config=config_data)
|
||||||
|
|
||||||
|
# sudo prompts:
|
||||||
|
try:
|
||||||
|
profile.attach_wireguard_configuration(new_config)
|
||||||
|
logger.info("changed the port. updating state..")
|
||||||
|
|
||||||
|
# UPDATE STATE:
|
||||||
|
state_repo = ProfileStateRepository()
|
||||||
|
state_repo.global_state.changed_port = True
|
||||||
|
|
||||||
|
return Result(valid=True, data=change_to)
|
||||||
|
except ProfileModificationError:
|
||||||
|
return Result(valid=False, error_type=ResultError.PERMISSON, message="Failed to swap the port due to permissions")
|
||||||
|
except CommandNotFoundError:
|
||||||
|
return Result(valid=False, error_type=ResultError.MISSING_SOFTWARE, message="Missing the command")
|
||||||
|
|
||||||
|
|
||||||
|
def port_was_changed_already_today() -> bool:
|
||||||
|
state_repo = ProfileStateRepository()
|
||||||
|
return state_repo.global_state.changed_port
|
||||||
|
|
||||||
|
|
||||||
|
def offer_port_change(profile: Profile) -> Result:
|
||||||
|
"""Offer the user the option to change the port"""
|
||||||
|
port_result = get_wg_port(profile)
|
||||||
|
|
||||||
|
if not port_result.valid:
|
||||||
|
return port_result
|
||||||
|
|
||||||
|
current_port = port_result.data
|
||||||
|
desired_port = get_random_different_port(current_port)
|
||||||
|
|
||||||
|
func_choice = partial(change_wg_port, profile=profile, desired_port=desired_port)
|
||||||
|
return Result(valid=False, function_choice=func_choice, error_type=ResultError.PORT, message=f"Failed to connect. Do you want to try to change the port to {desired_port}?")
|
||||||
|
|
||||||
|
|
||||||
|
def should_we_try_to_change_port():
|
||||||
|
"""Returns a boolean if we should change the port"""
|
||||||
|
if not ConfigurationController.do_we_allow_port_prompts():
|
||||||
|
logger.info("We do not allow port changes")
|
||||||
|
return False
|
||||||
|
|
||||||
|
state_repo = ProfileStateRepository()
|
||||||
|
if state_repo.global_state.changed_port:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ from core.observers.ConnectionObserver import ConnectionObserver
|
||||||
from core.controllers.SystemStateController import SystemStateController
|
from core.controllers.SystemStateController import SystemStateController
|
||||||
from core.observers.TicketObserver import TicketObserver
|
from core.observers.TicketObserver import TicketObserver
|
||||||
|
|
||||||
|
|
||||||
def establish_connection(
|
def establish_connection(
|
||||||
profile: Union[SessionProfile, SystemProfile],
|
profile: Union[SessionProfile, SystemProfile],
|
||||||
ignore: tuple[type[Exception]] = (),
|
ignore: tuple[type[Exception]] = (),
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from core.services.keys_and_verifications.wireguard_keys import get_mtu, insert_mtu_into_config_on_disk
|
from core.services.keys_and_verifications.wireguard_keys import get_mtu, insert_mtu_into_config_on_disk, offer_port_change, should_we_try_to_change_port, port_was_changed_already_today
|
||||||
from core.services.networking.systemwide.wireguard.mtu_tools import loop_through_testing_mtu, mtu_is_fine, MIN_MTU, MAX_MTU
|
from core.services.networking.systemwide.wireguard.mtu_tools import loop_through_testing_mtu, mtu_is_fine, MIN_MTU, MAX_MTU
|
||||||
from core.services.networking.general_connection_tools.location_evaluation import check_if_location_is_up
|
from core.services.networking.general_connection_tools.location_evaluation import check_if_location_is_up
|
||||||
from core.services.networking.api_requests.ApiResponseModel import BackoffStrategy
|
from core.services.networking.api_requests.ApiResponseModel import BackoffStrategy
|
||||||
|
|
@ -11,7 +11,6 @@ from core.services.networking.systemwide.systemwide_utils import extract_wg_inte
|
||||||
from core.observers.Notifications import Notifications, EventType
|
from core.observers.Notifications import Notifications, EventType
|
||||||
from core.services.networking.systemwide.general_tools.test_any_protocol import test_ip_systemwide_for_any_protocol
|
from core.services.networking.systemwide.general_tools.test_any_protocol import test_ip_systemwide_for_any_protocol
|
||||||
|
|
||||||
|
|
||||||
# from core.services.networking.general_connection_tools.config_tools import extract_endpoint_ip, extract_dns
|
# from core.services.networking.general_connection_tools.config_tools import extract_endpoint_ip, extract_dns
|
||||||
from core.services.networking.systemwide import killswitch
|
from core.services.networking.systemwide import killswitch
|
||||||
from core.services.networking.systemwide.dns_tools.process_dns_result import orchestrate_dns_check
|
from core.services.networking.systemwide.dns_tools.process_dns_result import orchestrate_dns_check
|
||||||
|
|
@ -292,7 +291,7 @@ def establish_system_connection(
|
||||||
ignore: tuple[type[Exception]] = (),
|
ignore: tuple[type[Exception]] = (),
|
||||||
connection_observer: Optional[ConnectionObserver] = None,
|
connection_observer: Optional[ConnectionObserver] = None,
|
||||||
ticket_observer: Optional[TicketObserver] = None # Passed in only for assassin's link to tickets in ConnectionController.establish_session_connection
|
ticket_observer: Optional[TicketObserver] = None # Passed in only for assassin's link to tickets in ConnectionController.establish_session_connection
|
||||||
):
|
) -> Result|None:
|
||||||
"""
|
"""
|
||||||
Establish connection with a maximum of 2 retries.
|
Establish connection with a maximum of 2 retries.
|
||||||
|
|
||||||
|
|
@ -339,11 +338,16 @@ def establish_system_connection(
|
||||||
raise ConnectionError(notification)
|
raise ConnectionError(notification)
|
||||||
|
|
||||||
except (ConnectionError, CalledProcessError) as e:
|
except (ConnectionError, CalledProcessError) as e:
|
||||||
_cleanup_on_error(firewall_setting, dns_setting)
|
|
||||||
|
# single time port is offered forever.
|
||||||
|
if port_was_changed_already_today():
|
||||||
|
# permanent config modify:
|
||||||
|
ConfigurationController.change_port_prompts(False)
|
||||||
|
|
||||||
if attempt == 1:
|
if attempt == 1:
|
||||||
# First failure: Evaluate Location and Internet without connection,
|
# First failure: Evaluate Location and Internet without connection,
|
||||||
terminate_system_connection(firewall_setting, dns_setting)
|
_cleanup_on_error(firewall_setting, dns_setting)
|
||||||
|
|
||||||
location_result = evaluate_location_and_internet(
|
location_result = evaluate_location_and_internet(
|
||||||
profile=profile,
|
profile=profile,
|
||||||
connection_observer=connection_observer
|
connection_observer=connection_observer
|
||||||
|
|
@ -364,11 +368,12 @@ def establish_system_connection(
|
||||||
continue # Proceed to attempt 2
|
continue # Proceed to attempt 2
|
||||||
|
|
||||||
elif attempt == 2:
|
elif attempt == 2:
|
||||||
# Second failure: Evaluate MTU
|
# Second failure: Evaluate MTU WITH connection on
|
||||||
current_mtu = get_mtu(profile_id=profile.id)
|
current_mtu = get_mtu(profile_id=profile.id)
|
||||||
|
|
||||||
if current_mtu != MAX_MTU or not current_mtu:
|
if current_mtu != MAX_MTU or not current_mtu:
|
||||||
logger.info("MTU is already at the max, no point to change it.")
|
logger.info("MTU is already at the max, no point to change it.")
|
||||||
|
_cleanup_on_error(firewall_setting, dns_setting)
|
||||||
raise ConnectionError("Failed to connect.")
|
raise ConnectionError("Failed to connect.")
|
||||||
# return Result(valid=False, error_type=ResultError.CONNECTION, message="Failed to connect.")
|
# return Result(valid=False, error_type=ResultError.CONNECTION, message="Failed to connect.")
|
||||||
|
|
||||||
|
|
@ -381,6 +386,7 @@ def establish_system_connection(
|
||||||
Notifications.emit(EventType.STATUS_UPDATE, notification)
|
Notifications.emit(EventType.STATUS_UPDATE, notification)
|
||||||
logger.info(notification)
|
logger.info(notification)
|
||||||
# If MTU wasn't changed or wasn't applicable, don't bother with attempt 3
|
# If MTU wasn't changed or wasn't applicable, don't bother with attempt 3
|
||||||
|
_cleanup_on_error(firewall_setting, dns_setting)
|
||||||
raise ConnectionError("Failed to connect.")
|
raise ConnectionError("Failed to connect.")
|
||||||
# return Result(valid=False, error_type=ResultError.CONNECTION, message="Failed to connect.")
|
# return Result(valid=False, error_type=ResultError.CONNECTION, message="Failed to connect.")
|
||||||
|
|
||||||
|
|
@ -394,16 +400,23 @@ def establish_system_connection(
|
||||||
else:
|
else:
|
||||||
logger.info("Skipping MTU testing, it's not relevant here.")
|
logger.info("Skipping MTU testing, it's not relevant here.")
|
||||||
|
|
||||||
# If MTU wasn't changed or wasn't applicable, don't bother with attempt 3
|
# If Port & MTU wasn't changed or wasn't applicable, don't bother with attempt 3
|
||||||
|
# PORT CHANGE:
|
||||||
|
if should_we_try_to_change_port():
|
||||||
|
print("We should try to change port")
|
||||||
|
_cleanup_on_error(firewall_setting, dns_setting)
|
||||||
|
return offer_port_change(profile)
|
||||||
|
else:
|
||||||
notification = "Failed to connect."
|
notification = "Failed to connect."
|
||||||
Notifications.emit(EventType.STATUS_UPDATE, notification)
|
Notifications.emit(EventType.STATUS_UPDATE, notification)
|
||||||
raise ConnectionError(notification)
|
_cleanup_on_error(firewall_setting, dns_setting)
|
||||||
# return Result(valid=False, error_type=ResultError.CONNECTION, message="Failed to connect.")
|
raise ConnectionError("Failed to connect.")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Attempt 3 failed
|
# Attempt 3 failed
|
||||||
logger.error("Maximum retries reached. Connection failed.")
|
logger.error("Maximum retries reached. Connection failed.")
|
||||||
notification = "Failed to connect."
|
notification = "Failed to connect."
|
||||||
Notifications.emit(EventType.STATUS_UPDATE, notification)
|
Notifications.emit(EventType.STATUS_UPDATE, notification)
|
||||||
|
_cleanup_on_error(firewall_setting, dns_setting)
|
||||||
raise ConnectionError(notification)
|
raise ConnectionError(notification)
|
||||||
# return Result(valid=False, error_type=ResultError.CONNECTION, message="Failed to connect.")
|
# return Result(valid=False, error_type=ResultError.CONNECTION, message="Failed to connect.")
|
||||||
|
|
|
||||||
|
|
@ -218,4 +218,3 @@ async def kill_9050() -> bool:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return kill_via_lsof(Constants.DEFAULT_TOR_PORT)
|
return kill_via_lsof(Constants.DEFAULT_TOR_PORT)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -120,7 +120,6 @@ def enable_profile(
|
||||||
ticket_observer: TicketObserver = None,
|
ticket_observer: TicketObserver = None,
|
||||||
max_resolution: Optional[str] = None
|
max_resolution: Optional[str] = None
|
||||||
) -> Result:
|
) -> Result:
|
||||||
|
|
||||||
"""Orchestrate Enabling both types of profiles"""
|
"""Orchestrate Enabling both types of profiles"""
|
||||||
|
|
||||||
# =========== ALREADY ENABLED ============
|
# =========== ALREADY ENABLED ============
|
||||||
|
|
@ -161,20 +160,14 @@ def enable_profile(
|
||||||
Notifications.emit(EventType.STATUS_UPDATE, notification)
|
Notifications.emit(EventType.STATUS_UPDATE, notification)
|
||||||
return
|
return
|
||||||
|
|
||||||
if connection_result.valid:
|
if not connection_result.valid:
|
||||||
|
return connection_result
|
||||||
|
|
||||||
if profile_observer is not None:
|
if profile_observer is not None:
|
||||||
profile_observer.notify('enabled', profile)
|
profile_observer.notify('enabled', profile)
|
||||||
notification = "Enabled."
|
notification = "Enabled."
|
||||||
Notifications.emit(EventType.STATUS_UPDATE, notification)
|
Notifications.emit(EventType.STATUS_UPDATE, notification)
|
||||||
|
|
||||||
return Result(valid=True, data=profile.type)
|
return Result(valid=True, data=profile.type)
|
||||||
else:
|
|
||||||
error_msg = connection_result.message
|
|
||||||
logger.error(error_msg)
|
|
||||||
notification = "Connection Failed."
|
|
||||||
Notifications.emit(EventType.STATUS_UPDATE, notification)
|
|
||||||
return Result(valid=False, error_type=ResultError.UNKNOWN, message=error_msg)
|
|
||||||
# raise ProfileActivationError(error_msg)
|
|
||||||
except FirewallError:
|
except FirewallError:
|
||||||
return Result(valid=False, error_type=ResultError.FIREWALL, message="Firewall Error.")
|
return Result(valid=False, error_type=ResultError.FIREWALL, message="Firewall Error.")
|
||||||
except ConnectionError as e:
|
except ConnectionError as e:
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ def read_a_file(path_of_file: str) -> str | bool:
|
||||||
file_path = Path(path_of_file)
|
file_path = Path(path_of_file)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(file_path, "r") as file:
|
with open(file_path, "r", encoding="utf-8") as file:
|
||||||
content = file.read()
|
content = file.read()
|
||||||
return content
|
return content
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue