systemwide wg goes connect -> test -> location check -> mtu check -> port check -> wg renegotiate
This commit is contained in:
parent
25d69d728c
commit
14e257d091
9 changed files with 643 additions and 256 deletions
|
|
@ -57,6 +57,7 @@ class ResultError(Enum):
|
||||||
NODE_ERROR = "node_error"
|
NODE_ERROR = "node_error"
|
||||||
NO_INTERNET = "no_internet"
|
NO_INTERNET = "no_internet"
|
||||||
PORT = "port"
|
PORT = "port"
|
||||||
|
RENEGOTIATION = "renegotiation"
|
||||||
NAVIGATE_TO = "navigate_to"
|
NAVIGATE_TO = "navigate_to"
|
||||||
UNKNOWN = "unknown"
|
UNKNOWN = "unknown"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -62,12 +62,18 @@ def establish_connection(
|
||||||
if profile.connection.code == "wireguard":
|
if profile.connection.code == "wireguard":
|
||||||
_ensure_wireguard_configured(profile, connection_observer)
|
_ensure_wireguard_configured(profile, connection_observer)
|
||||||
|
|
||||||
establish_fn = {
|
|
||||||
ProfileType.SESSION: ConnectionController.establish_session_connection,
|
if profile.connection.code == "wireguard" and profile.type == ProfileType.SYSTEM:
|
||||||
ProfileType.SYSTEM: establish_system_connection,
|
logger.info("Doing a systemwide WG connection..")
|
||||||
}[profile.type]
|
return establish_system_connection(profile, ignore, connection_observer, ticket_observer)
|
||||||
|
|
||||||
return _establish_with_renegotiation(profile, establish_fn, ignore, connection_observer, ticket_observer)
|
else:
|
||||||
|
establish_fn = {
|
||||||
|
ProfileType.SESSION: ConnectionController.establish_session_connection,
|
||||||
|
ProfileType.SYSTEM: establish_system_connection,
|
||||||
|
}[profile.type]
|
||||||
|
|
||||||
|
return _establish_with_renegotiation(profile, establish_fn, ignore, connection_observer, ticket_observer)
|
||||||
|
|
||||||
|
|
||||||
def _ensure_proxy_configured(
|
def _ensure_proxy_configured(
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,8 @@
|
||||||
|
from core.services.networking.api_requests.subtools.extract_domain import extract_domain
|
||||||
|
|
||||||
from core.services.networking.httpx.classify_response import classify_response
|
from core.services.networking.httpx.classify_response import classify_response
|
||||||
|
from core.observers.Notifications import Notifications, EventType
|
||||||
|
from core.utils.run_commands import run_generic_command
|
||||||
|
|
||||||
# models
|
# models
|
||||||
from core.models.Result import Result, ResultError
|
from core.models.Result import Result, ResultError
|
||||||
|
|
@ -6,7 +10,9 @@ from core.services.networking.api_requests.ApiResponseModel import ApiResponse,
|
||||||
|
|
||||||
from core.Constants import Constants
|
from core.Constants import Constants
|
||||||
from core.errors.logger import logger
|
from core.errors.logger import logger
|
||||||
from essentials.observers.ConnectionObserver import ConnectionObserver
|
# from essentials.observers.ConnectionObserver import ConnectionObserver
|
||||||
|
from core.observers.ConnectionObserver import ConnectionObserver
|
||||||
|
|
||||||
|
|
||||||
# generic
|
# generic
|
||||||
import time
|
import time
|
||||||
|
|
@ -31,69 +37,339 @@ API_ENDPOINT_FORMATS = {
|
||||||
|
|
||||||
MAX_ATTEMPTS = 3
|
MAX_ATTEMPTS = 3
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import time
|
||||||
|
import socket
|
||||||
|
import multiprocessing
|
||||||
|
from threading import Thread
|
||||||
|
from queue import Queue, Empty
|
||||||
|
|
||||||
|
def is_dns_problem_worker(url: str, queue: multiprocessing.Queue):
|
||||||
|
"""Worker function for a separate PROCESS"""
|
||||||
|
domain = extract_domain(url)
|
||||||
|
try:
|
||||||
|
socket.gethostbyname(domain)
|
||||||
|
queue.put((url, False)) # Resolved
|
||||||
|
except Exception:
|
||||||
|
queue.put((url, True)) # Failed
|
||||||
|
|
||||||
def test(connection_observer: Optional[ConnectionObserver], port: Optional[int] = None) -> Result:
|
def test(connection_observer: Optional[ConnectionObserver], port: Optional[int] = None) -> Result:
|
||||||
if connection_observer:
|
|
||||||
connection_observer.notify("connecting", "Testing IP..")
|
|
||||||
return asyncio.run(_test(connection_observer, port))
|
|
||||||
|
|
||||||
|
|
||||||
async def _test(connection_observer: Optional[ConnectionObserver], port: Optional[int] = None) -> Result:
|
|
||||||
endpoint_list = list(API_ENDPOINT_FORMATS.keys())[:MAX_ATTEMPTS]
|
endpoint_list = list(API_ENDPOINT_FORMATS.keys())[:MAX_ATTEMPTS]
|
||||||
|
start_time = time.time()
|
||||||
|
total_budget = 5.0
|
||||||
|
|
||||||
|
# 1. NUCLEAR DNS PRE-FLIGHT (Using Processes, not Threads)
|
||||||
|
if port is None:
|
||||||
|
dns_queue = multiprocessing.Queue()
|
||||||
|
processes = []
|
||||||
|
|
||||||
|
for url in endpoint_list:
|
||||||
|
p = multiprocessing.Process(target=is_dns_problem_worker, args=(url, dns_queue))
|
||||||
|
p.start()
|
||||||
|
processes.append(p)
|
||||||
|
|
||||||
|
any_resolved = False
|
||||||
|
dns_timeout = 2
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Wait for the first successful resolution
|
||||||
|
while time.time() - start_time < dns_timeout:
|
||||||
|
try:
|
||||||
|
# Non-blocking check of the queue
|
||||||
|
url, is_problem = dns_queue.get(timeout=0.1)
|
||||||
|
if not is_problem:
|
||||||
|
any_resolved = True
|
||||||
|
break
|
||||||
|
except Empty:
|
||||||
|
continue
|
||||||
|
finally:
|
||||||
|
# THE NUCLEAR STRIKE:
|
||||||
|
# We kill every single process immediately.
|
||||||
|
# The OS kernel wipes them from memory regardless of where they are stuck.
|
||||||
|
for p in processes:
|
||||||
|
p.terminate()
|
||||||
|
p.join() # Clean up the zombie process entry
|
||||||
|
|
||||||
|
if not any_resolved:
|
||||||
|
logger.error("Aggressive DNS check failed: No internet.")
|
||||||
|
return Result(valid=False, error_type=ResultError.CONNECTION, message="No internet connection.")
|
||||||
|
|
||||||
|
# 2. HTTP REQUEST PHASE (Using Daemon Threads)
|
||||||
|
transport = None
|
||||||
if port:
|
if port:
|
||||||
transport = httpx_socks.AsyncProxyTransport(
|
transport = httpx_socks.ProxyTransport(
|
||||||
proxy_type=ProxyType.SOCKS5,
|
proxy_type=ProxyType.SOCKS5,
|
||||||
proxy_host="127.0.0.1",
|
proxy_host="127.0.0.1",
|
||||||
proxy_port=port,
|
proxy_port=port,
|
||||||
)
|
)
|
||||||
client = httpx.AsyncClient(transport=transport, http2=True, timeout=6)
|
|
||||||
else:
|
|
||||||
client = httpx.AsyncClient(http2=True, timeout=6)
|
|
||||||
|
|
||||||
async def attempt_request(url: str, index: int):
|
# We use a Queue to get the first successful Result back from daemon threads
|
||||||
logger.info(f"Attempt {index + 1}: Trying {url}")
|
result_queue = Queue()
|
||||||
# if connection_observer:
|
|
||||||
# connection_observer.notify("connecting", f"Testing IP. Attempt {index + 1}")
|
def attempt_request(url: str, index: int, client: httpx.Client):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
raw_response = await client.get(url)
|
raw_response = client.get(url)
|
||||||
filtered_response = classify_response(raw_response)
|
filtered_response = classify_response(raw_response)
|
||||||
|
|
||||||
if filtered_response.valid:
|
if filtered_response.valid:
|
||||||
ip_address = extract_ip_from_response(
|
ip_address = extract_ip_from_response(response=filtered_response.data, api_url=url)
|
||||||
response=filtered_response.data,
|
if ip_address:
|
||||||
api_url=url
|
result_queue.put(Result(valid=True, data=ip_address))
|
||||||
)
|
return
|
||||||
if ip_address is not None:
|
except Exception:
|
||||||
return Result(valid=True, data=ip_address)
|
pass
|
||||||
|
|
||||||
logger.error(f"Skipping {url}")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Skipping {url}. Invalid API reply or failed connection {str(e)}")
|
|
||||||
|
|
||||||
return Result(valid=False)
|
|
||||||
|
|
||||||
try:
|
with httpx.Client(transport=transport, http2=False, timeout=3.0) as client:
|
||||||
# Create tasks for all endpoints to run them concurrently
|
for i, url in enumerate(endpoint_list):
|
||||||
tasks = [asyncio.create_task(attempt_request(url, i)) for i, url in enumerate(endpoint_list)]
|
# daemon=True is CRITICAL.
|
||||||
|
# It tells Python: "If the main program exits, kill this thread immediately."
|
||||||
# Process tasks as they complete
|
t = Thread(target=attempt_request, args=(url, i, client), daemon=True)
|
||||||
for future in asyncio.as_completed(tasks):
|
t.start()
|
||||||
result = await future
|
|
||||||
if result.valid:
|
|
||||||
# Cancel all other pending tasks immediately
|
|
||||||
for t in tasks:
|
|
||||||
if not t.done():
|
|
||||||
t.cancel()
|
|
||||||
return result
|
|
||||||
|
|
||||||
finally:
|
# Wait for the first success or the remaining budget
|
||||||
await client.aclose()
|
while True:
|
||||||
|
elapsed = time.time() - start_time
|
||||||
|
remaining = total_budget - elapsed
|
||||||
|
if remaining <= 0:
|
||||||
|
break
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Block for the remaining budget
|
||||||
|
return result_queue.get(timeout=remaining)
|
||||||
|
except Empty:
|
||||||
|
break
|
||||||
|
|
||||||
logger.error(f"We went through {len(endpoint_list)} attempts and could not get a reply.")
|
logger.error(f"We went through {len(endpoint_list)} attempts and could not get a reply.")
|
||||||
return Result(valid=False, error_type=ResultError.CONNECTION, message="Could not connect.")
|
return Result(valid=False, error_type=ResultError.CONNECTION, message="Could not connect.")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# import time
|
||||||
|
# import socket
|
||||||
|
# from concurrent.futures import ThreadPoolExecutor, wait, FIRST_COMPLETED
|
||||||
|
|
||||||
|
# def is_dns_problem(url: str) -> bool:
|
||||||
|
# """Regular DNS check for domains"""
|
||||||
|
# domain = extract_domain(url)
|
||||||
|
# try:
|
||||||
|
# socket.gethostbyname(domain)
|
||||||
|
# return False
|
||||||
|
# except (socket.gaierror, Exception):
|
||||||
|
# return True
|
||||||
|
|
||||||
|
# def test(connection_observer: Optional[ConnectionObserver], port: Optional[int] = None) -> Result:
|
||||||
|
# endpoint_list = list(API_ENDPOINT_FORMATS.keys())[:MAX_ATTEMPTS]
|
||||||
|
# start_time = time.time()
|
||||||
|
# total_budget = 4.0
|
||||||
|
|
||||||
|
# # 1. AGGRESSIVE DNS PRE-FLIGHT
|
||||||
|
# if port is None:
|
||||||
|
# # IMPORTANT: Do NOT use 'with' here. 'with' calls shutdown(wait=True),
|
||||||
|
# # which causes the hang you are seeing.
|
||||||
|
# dns_executor = ThreadPoolExecutor(max_workers=len(endpoint_list))
|
||||||
|
# dns_futures = {dns_executor.submit(is_dns_problem, url): url for url in endpoint_list}
|
||||||
|
|
||||||
|
# dns_done, _ = wait(dns_futures, timeout=1.2, return_when=FIRST_COMPLETED)
|
||||||
|
|
||||||
|
# any_resolved = False
|
||||||
|
# for future in dns_done:
|
||||||
|
# if future.result() is False:
|
||||||
|
# any_resolved = True
|
||||||
|
# break
|
||||||
|
|
||||||
|
# if not any_resolved:
|
||||||
|
# logger.error("Aggressive DNS check failed: No internet or DNS unreachable.")
|
||||||
|
# # Return immediately. The dns_executor threads will stay alive
|
||||||
|
# # in the background until the OS times them out, but the
|
||||||
|
# # main thread is now free.
|
||||||
|
# return Result(valid=False, error_type=ResultError.CONNECTION, message="No internet connection.")
|
||||||
|
|
||||||
|
# # 2. HTTP REQUEST PHASE
|
||||||
|
# transport = None
|
||||||
|
# if port:
|
||||||
|
# transport = httpx_socks.ProxyTransport(
|
||||||
|
# proxy_type=ProxyType.SOCKS5,
|
||||||
|
# proxy_host="127.0.0.1",
|
||||||
|
# proxy_port=port,
|
||||||
|
# )
|
||||||
|
|
||||||
|
# with httpx.Client(transport=transport, http2=False, timeout=3.0) as client:
|
||||||
|
# def attempt_request(url: str, index: int):
|
||||||
|
# try:
|
||||||
|
# raw_response = client.get(url)
|
||||||
|
# filtered_response = classify_response(raw_response)
|
||||||
|
# if filtered_response.valid:
|
||||||
|
# ip_address = extract_ip_from_response(response=filtered_response.data, api_url=url)
|
||||||
|
# if ip_address:
|
||||||
|
# return Result(valid=True, data=ip_address)
|
||||||
|
# except Exception:
|
||||||
|
# pass
|
||||||
|
# return Result(valid=False)
|
||||||
|
|
||||||
|
# # IMPORTANT: Do NOT use 'with' here either.
|
||||||
|
# http_executor = ThreadPoolExecutor(max_workers=len(endpoint_list))
|
||||||
|
# http_futures = {http_executor.submit(attempt_request, url, i): url for i, url in enumerate(endpoint_list)}
|
||||||
|
|
||||||
|
# while http_futures:
|
||||||
|
# elapsed = time.time() - start_time
|
||||||
|
# remaining = total_budget - elapsed
|
||||||
|
|
||||||
|
# if remaining <= 0:
|
||||||
|
# break
|
||||||
|
|
||||||
|
# done, _ = wait(http_futures, timeout=remaining, return_when=FIRST_COMPLETED)
|
||||||
|
|
||||||
|
# if not done:
|
||||||
|
# break
|
||||||
|
|
||||||
|
# for future in done:
|
||||||
|
# result = future.result()
|
||||||
|
# if result.valid:
|
||||||
|
# return result
|
||||||
|
# http_futures.remove(future)
|
||||||
|
|
||||||
|
# logger.error(f"We went through {len(endpoint_list)} attempts and could not get a reply.")
|
||||||
|
# return Result(valid=False, error_type=ResultError.CONNECTION, message="Could not connect.")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# async
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# async def _test(connection_observer: Optional[ConnectionObserver], port: Optional[int] = None) -> Result:
|
||||||
|
# endpoint_list = list(API_ENDPOINT_FORMATS.keys())[:MAX_ATTEMPTS]
|
||||||
|
|
||||||
|
# # Using 'async with' ensures the client and its connection pool are
|
||||||
|
# # closed before the event loop starts shutting down.
|
||||||
|
# async with httpx.AsyncClient(
|
||||||
|
# transport=httpx_socks.AsyncProxyTransport(
|
||||||
|
# proxy_type=ProxyType.SOCKS5,
|
||||||
|
# proxy_host="127.0.0.1",
|
||||||
|
# proxy_port=port,
|
||||||
|
# ) if port else None,
|
||||||
|
# http2=True,
|
||||||
|
# timeout=4
|
||||||
|
# ) as client:
|
||||||
|
|
||||||
|
# async def attempt_request(url: str, index: int):
|
||||||
|
# logger.info(f"Attempt {index + 1}: Trying {url}")
|
||||||
|
# try:
|
||||||
|
# raw_response = await client.get(url)
|
||||||
|
# filtered_response = classify_response(raw_response)
|
||||||
|
|
||||||
|
# if filtered_response.valid:
|
||||||
|
# ip_address = extract_ip_from_response(
|
||||||
|
# response=filtered_response.data,
|
||||||
|
# api_url=url
|
||||||
|
# )
|
||||||
|
# if ip_address is not None:
|
||||||
|
# return Result(valid=True, data=ip_address)
|
||||||
|
|
||||||
|
# logger.error(f"Skipping {url}")
|
||||||
|
# except Exception as e:
|
||||||
|
# logger.error(f"Skipping {url}. Invalid API reply or failed connection {str(e)}")
|
||||||
|
|
||||||
|
# return Result(valid=False)
|
||||||
|
|
||||||
|
# # Create tasks for concurrent execution
|
||||||
|
# tasks = [asyncio.create_task(attempt_request(url, i)) for i, url in enumerate(endpoint_list)]
|
||||||
|
|
||||||
|
# try:
|
||||||
|
# for future in asyncio.as_completed(tasks):
|
||||||
|
# result = await future
|
||||||
|
# if result.valid:
|
||||||
|
# # 1. Signal all other pending tasks to stop
|
||||||
|
# for t in tasks:
|
||||||
|
# if not t.done():
|
||||||
|
# t.cancel()
|
||||||
|
|
||||||
|
# # 2. CRITICAL: Wait for the cancelled tasks to actually finish.
|
||||||
|
# # return_exceptions=True prevents CancelledError from being raised here.
|
||||||
|
# await asyncio.gather(*tasks, return_exceptions=True)
|
||||||
|
# return result
|
||||||
|
# except Exception as e:
|
||||||
|
# logger.error(f"Unexpected error during request processing: {e}")
|
||||||
|
|
||||||
|
# # This part is reached only if no task returned a valid Result
|
||||||
|
# logger.error(f"We went through {len(endpoint_list)} attempts and could not get a reply.")
|
||||||
|
# return Result(valid=False, error_type=ResultError.CONNECTION, message="Could not connect.")
|
||||||
|
|
||||||
|
|
||||||
|
# def test(connection_observer: Optional[ConnectionObserver], port: Optional[int] = None) -> Result:
|
||||||
|
# if connection_observer:
|
||||||
|
# connection_observer.notify("connecting", "Testing IP..")
|
||||||
|
# return asyncio.run(_test(connection_observer, port))
|
||||||
|
|
||||||
|
|
||||||
|
# async def _test(connection_observer: Optional[ConnectionObserver], port: Optional[int] = None) -> Result:
|
||||||
|
# endpoint_list = list(API_ENDPOINT_FORMATS.keys())[:MAX_ATTEMPTS]
|
||||||
|
|
||||||
|
# if port:
|
||||||
|
# transport = httpx_socks.AsyncProxyTransport(
|
||||||
|
# proxy_type=ProxyType.SOCKS5,
|
||||||
|
# proxy_host="127.0.0.1",
|
||||||
|
# proxy_port=port,
|
||||||
|
# )
|
||||||
|
# client = httpx.AsyncClient(transport=transport, http2=True, timeout=4)
|
||||||
|
# else:
|
||||||
|
# client = httpx.AsyncClient(http2=True, timeout=4)
|
||||||
|
|
||||||
|
# async def attempt_request(url: str, index: int):
|
||||||
|
# logger.info(f"Attempt {index + 1}: Trying {url}")
|
||||||
|
|
||||||
|
# try:
|
||||||
|
# raw_response = await client.get(url)
|
||||||
|
# filtered_response = classify_response(raw_response)
|
||||||
|
|
||||||
|
# if filtered_response.valid:
|
||||||
|
# ip_address = extract_ip_from_response(
|
||||||
|
# response=filtered_response.data,
|
||||||
|
# api_url=url
|
||||||
|
# )
|
||||||
|
# if ip_address is not None:
|
||||||
|
# return Result(valid=True, data=ip_address)
|
||||||
|
|
||||||
|
# logger.error(f"Skipping {url}")
|
||||||
|
# except Exception as e:
|
||||||
|
# logger.error(f"Skipping {url}. Invalid API reply or failed connection {str(e)}")
|
||||||
|
|
||||||
|
# return Result(valid=False)
|
||||||
|
|
||||||
|
# try:
|
||||||
|
# # Create tasks for all endpoints to run them concurrently
|
||||||
|
# tasks = [asyncio.create_task(attempt_request(url, i)) for i, url in enumerate(endpoint_list)]
|
||||||
|
|
||||||
|
# # Process tasks as they complete
|
||||||
|
# for future in asyncio.as_completed(tasks):
|
||||||
|
# result = await future
|
||||||
|
# if result.valid:
|
||||||
|
# # Cancel all other pending tasks immediately
|
||||||
|
# for t in tasks:
|
||||||
|
# if not t.done():
|
||||||
|
# t.cancel()
|
||||||
|
# return result
|
||||||
|
|
||||||
|
# finally:
|
||||||
|
# await client.aclose()
|
||||||
|
|
||||||
|
# logger.error(f"We went through {len(endpoint_list)} attempts and could not get a reply.")
|
||||||
|
# return Result(valid=False, error_type=ResultError.CONNECTION, message="Could not connect.")
|
||||||
|
|
||||||
|
|
||||||
def extract_ip_from_response(response: dict, api_url: str) -> str | bool:
|
def extract_ip_from_response(response: dict, api_url: str) -> str | bool:
|
||||||
"""
|
"""
|
||||||
Purpose:
|
Purpose:
|
||||||
|
|
@ -124,6 +400,7 @@ def extract_ip_from_response(response: dict, api_url: str) -> str | bool:
|
||||||
def ping_ip(ip: str) -> Result:
|
def ping_ip(ip: str) -> Result:
|
||||||
command = ["ping", "-c", "1", "-W", "2", ip]
|
command = ["ping", "-c", "1", "-W", "2", ip]
|
||||||
human_readable_goal = f"Pinging {ip}"
|
human_readable_goal = f"Pinging {ip}"
|
||||||
|
Notifications.emit(EventType.STATUS_UPDATE, human_readable_goal)
|
||||||
return run_generic_command(command, human_readable_goal, timeout=5)
|
return run_generic_command(command, human_readable_goal, timeout=5)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
from core.services.networking.api_requests.ApiResponseModel import BackoffStrategy
|
from core.services.networking.api_requests.ApiResponseModel import BackoffStrategy
|
||||||
from core.services.networking.general_connection_tools.test_internet import test_internet
|
from core.services.networking.general_connection_tools.test_internet import test_internet
|
||||||
from core.services.networking.general_connection_tools.ip import ping_ip_worked
|
from core.services.networking.general_connection_tools.ip import ping_ip
|
||||||
from core.services.networking.systemwide.general_tools.test_any_protocol import get_singbox_ip
|
from core.services.networking.systemwide.general_tools.test_any_protocol import get_singbox_ip
|
||||||
from core.services.networking.general_connection_tools.config_tools import extract_endpoint_ip
|
from core.services.networking.general_connection_tools.config_tools import extract_endpoint_ip
|
||||||
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
|
||||||
|
|
@ -47,7 +47,8 @@ def check_if_location_is_up(profile: Profile, connection_observer: ConnectionObs
|
||||||
ip_address = get_profile_ip(profile_id=profile_id, protocol=protocol, profile_type=profile_type)
|
ip_address = get_profile_ip(profile_id=profile_id, protocol=protocol, profile_type=profile_type)
|
||||||
|
|
||||||
if ip_address:
|
if ip_address:
|
||||||
can_be_pinged = ping_ip_worked(ip_address)
|
ping_result = ping_ip(ip_address)
|
||||||
|
can_be_pinged = ping_result.valid
|
||||||
print(f"Can {location_name} be pinged? {can_be_pinged}")
|
print(f"Can {location_name} be pinged? {can_be_pinged}")
|
||||||
else:
|
else:
|
||||||
logger.error("Skipping IP Address Test.")
|
logger.error("Skipping IP Address Test.")
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
from core.utils.run_commands import run_generic_command
|
||||||
|
|
||||||
from core.Constants import Constants
|
from core.Constants import Constants
|
||||||
from core.Errors import CommandNotFoundError
|
from core.Errors import CommandNotFoundError
|
||||||
from core.observers.ConnectionObserver import ConnectionObserver
|
from core.observers.ConnectionObserver import ConnectionObserver
|
||||||
|
|
@ -37,15 +39,33 @@ def await_network_interface():
|
||||||
raise ConnectionError('The network interface could not be activated.')
|
raise ConnectionError('The network interface could not be activated.')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def system_uses_wireguard_interface():
|
def system_uses_wireguard_interface():
|
||||||
|
|
||||||
if shutil.which('ip') is None:
|
if shutil.which('ip') is None:
|
||||||
raise CommandNotFoundError('ip')
|
raise CommandNotFoundError('ip')
|
||||||
|
|
||||||
process = subprocess.Popen(('ip', 'route', 'get', '192.0.2.1'), stdout=subprocess.PIPE)
|
command = ['ip', 'route', 'get', '192.0.2.1']
|
||||||
process_output = str(process.stdout.read())
|
human_readable_goal = "Getting WG interface"
|
||||||
|
get_output = run_generic_command(command, human_readable_goal, timeout=8)
|
||||||
|
if not get_output.valid:
|
||||||
|
return get_output
|
||||||
|
|
||||||
return bool(re.search('dev wg', str(process_output)))
|
output = get_output.data
|
||||||
|
print(f"output is {output}")
|
||||||
|
return bool(re.search('dev wg', str(output)))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# def system_uses_wireguard_interface():
|
||||||
|
|
||||||
|
# if shutil.which('ip') is None:
|
||||||
|
# raise CommandNotFoundError('ip')
|
||||||
|
|
||||||
|
# process = subprocess.Popen(('ip', 'route', 'get', '192.0.2.1'), stdout=subprocess.PIPE)
|
||||||
|
# process_output = str(process.stdout.read())
|
||||||
|
|
||||||
|
# return bool(re.search('dev wg', str(process_output)))
|
||||||
|
|
||||||
|
|
||||||
def __test_connection(port_number: Optional[int] = None, timeout: float = 4.0):
|
def __test_connection(port_number: Optional[int] = None, timeout: float = 4.0):
|
||||||
|
|
|
||||||
66
core/services/networking/mtu/mtu_orchestrator.py
Normal file
66
core/services/networking/mtu/mtu_orchestrator.py
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
from core.services.keys_and_verifications.wireguard_keys import get_mtu, insert_mtu_into_config_on_disk
|
||||||
|
from core.services.networking.mtu.mtu_tools import loop_through_testing_mtu, mtu_is_fine, MIN_MTU, MAX_MTU
|
||||||
|
from core.observers.Notifications import Notifications, EventType
|
||||||
|
from core.models.BaseProfile import BaseProfile as Profile
|
||||||
|
from core.models.system.SystemProfile import SystemProfile
|
||||||
|
from core.errors.logger import logger
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
def _changed_mtu(profile: SystemProfile, current_mtu: Optional[int]) -> bool:
|
||||||
|
notification = "Testing MTU.."
|
||||||
|
Notifications.emit(EventType.STATUS_UPDATE, notification)
|
||||||
|
|
||||||
|
# optimal_mtu = loop_through_testing_mtu()
|
||||||
|
optimal_mtu = asyncio.run(loop_through_testing_mtu())
|
||||||
|
logger.info(f"Optimal MTU is {optimal_mtu}")
|
||||||
|
|
||||||
|
if optimal_mtu == current_mtu:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Assuming it isn't already at the max, there's no need to reset it to the max,
|
||||||
|
if current_mtu != MAX_MTU and optimal_mtu == MAX_MTU:
|
||||||
|
return False
|
||||||
|
|
||||||
|
notification = f"Updating WG MTU with {optimal_mtu}"
|
||||||
|
Notifications.emit(EventType.STATUS_UPDATE, notification)
|
||||||
|
return insert_mtu_into_config_on_disk(profile=profile, mtu=optimal_mtu)
|
||||||
|
|
||||||
|
def should_we_even_test_mtu(profile_id: int):
|
||||||
|
# Evaluate MTU WITH connection on
|
||||||
|
current_mtu = get_mtu(profile_id=profile_id)
|
||||||
|
|
||||||
|
if not current_mtu:
|
||||||
|
return True, current_mtu
|
||||||
|
|
||||||
|
if current_mtu > MIN_MTU:
|
||||||
|
logger.info("Skipping MTU testing, because MTU as at the minimum, so it's not relevant here.")
|
||||||
|
return False, current_mtu
|
||||||
|
else:
|
||||||
|
logger.info("MTU isn't at the min, so we can potentially improve")
|
||||||
|
return True, current_mtu
|
||||||
|
|
||||||
|
def was_mtu_changed(profile: Profile, current_mtu: int):
|
||||||
|
notification = "Testing MTU..."
|
||||||
|
Notifications.emit(EventType.STATUS_UPDATE, notification)
|
||||||
|
logger.info(notification)
|
||||||
|
|
||||||
|
if mtu_is_fine(current_mtu):
|
||||||
|
notification = "MTU is NOT the issue."
|
||||||
|
Notifications.emit(EventType.STATUS_UPDATE, notification)
|
||||||
|
logger.info(notification)
|
||||||
|
|
||||||
|
# MTU wasn't changed or wasn't applicable,
|
||||||
|
return False
|
||||||
|
|
||||||
|
if _changed_mtu(profile=profile, current_mtu=current_mtu):
|
||||||
|
logger.info("MTU changed. We should retry the connection")
|
||||||
|
return True
|
||||||
|
|
||||||
|
else:
|
||||||
|
logger.warning("MTU check performed but no change was made or user rejected it.")
|
||||||
|
notification = "MTU was NOT changed."
|
||||||
|
Notifications.emit(EventType.STATUS_UPDATE, notification)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
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.keys_and_verifications.wireguard_keys import register_wireguard_session
|
||||||
from core.services.networking.systemwide.wireguard.mtu_tools import loop_through_testing_mtu, mtu_is_fine, MIN_MTU, MAX_MTU
|
from core.services.keys_and_verifications.wireguard_keys import offer_port_change, should_we_try_to_change_port #, port_was_changed_already_today
|
||||||
|
from core.services.networking.mtu.mtu_orchestrator import should_we_even_test_mtu, was_mtu_changed
|
||||||
|
|
||||||
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
|
||||||
from core.models.enum_models import ProtocolChoice, SINGBOX_PROTOCOLS
|
from core.models.enum_models import ProtocolChoice, SINGBOX_PROTOCOLS
|
||||||
|
|
@ -31,19 +33,20 @@ from core.controllers.SystemStateController import SystemStateController
|
||||||
from core.models.system.SystemProfile import SystemProfile
|
from core.models.system.SystemProfile import SystemProfile
|
||||||
from core.models.system.SystemState import SystemState
|
from core.models.system.SystemState import SystemState
|
||||||
from core.observers.ConnectionObserver import ConnectionObserver
|
from core.observers.ConnectionObserver import ConnectionObserver
|
||||||
from subprocess import CalledProcessError
|
# from subprocess import CalledProcessError
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
# import os
|
# import os
|
||||||
# import re
|
# import re
|
||||||
import shutil
|
import shutil
|
||||||
# import subprocess
|
# import subprocess
|
||||||
import time
|
import time
|
||||||
import asyncio
|
# import asyncio
|
||||||
|
from functools import partial
|
||||||
|
|
||||||
def terminate_system_connection(
|
def terminate_system_connection(
|
||||||
firewall_setting: Optional[bool] = None,
|
firewall_setting: Optional[bool] = None,
|
||||||
dns_setting: Optional[bool] = None
|
dns_setting: Optional[bool] = None
|
||||||
):
|
) -> Result:
|
||||||
|
|
||||||
# ====== PREREQS =======
|
# ====== PREREQS =======
|
||||||
if firewall_setting is None:
|
if firewall_setting is None:
|
||||||
|
|
@ -63,42 +66,43 @@ def terminate_system_connection(
|
||||||
if dns_setting:
|
if dns_setting:
|
||||||
_ = revert_dns()
|
_ = revert_dns()
|
||||||
|
|
||||||
# ====== NMCLI =======
|
# ====== SINGBOX =======
|
||||||
|
# Check if the user is switching from Singbox to Wireguard:
|
||||||
if SystemStateController.exists():
|
if SystemStateController.exists():
|
||||||
|
|
||||||
# ====== SINGBOX =======
|
|
||||||
# Check if the user is switching from Singbox to Wireguard:
|
|
||||||
current_state = SystemState.get()
|
current_state = SystemState.get()
|
||||||
if get_protocol_enum(current_state.profile_id) in SINGBOX_PROTOCOLS:
|
if current_state:
|
||||||
logger.info("Ending Singbox protocols first..")
|
if get_protocol_enum(current_state.profile_id) in SINGBOX_PROTOCOLS:
|
||||||
from core.services.networking.systemwide.encrypted_proxy.singbox_runner import end_singbox
|
logger.info("Ending Singbox protocols first..")
|
||||||
return end_singbox()
|
from core.services.networking.systemwide.encrypted_proxy.singbox_runner import end_singbox
|
||||||
|
return end_singbox()
|
||||||
|
|
||||||
# ====== FINISH WIREGUARD =======
|
# ====== FINISH WIREGUARD =======
|
||||||
command = ['nmcli', 'connection', 'delete', 'wg']
|
command = ['nmcli', 'connection', 'delete', 'wg']
|
||||||
human_readable_goal = "Deleting NMCLI wg interface"
|
human_readable_goal = "Deleting NMCLI wg interface"
|
||||||
delete_completed = run_generic_command(command, human_readable_goal, timeout=8)
|
delete_completed = run_generic_command(command, human_readable_goal, timeout=8)
|
||||||
|
print(f"delete_completed output: {delete_completed}")
|
||||||
|
|
||||||
if delete_completed.valid or not system_uses_wireguard_interface():
|
if not system_uses_wireguard_interface():
|
||||||
command = ['nmcli', 'connection', 'delete', 'hv-ipv6-sink']
|
command = ['nmcli', 'connection', 'delete', 'hv-ipv6-sink']
|
||||||
human_readable_goal = "Deleting NMCLI IPv6 Sink"
|
human_readable_goal = "Deleting NMCLI IPv6 Sink"
|
||||||
sinkhole_deleted = run_generic_command(command, human_readable_goal, timeout=8)
|
sinkhole_deleted = run_generic_command(command, human_readable_goal, timeout=8)
|
||||||
if not sinkhole_deleted.valid:
|
if not sinkhole_deleted.valid:
|
||||||
logger.error(f"IPv6 Sinkhole Delete Error: {sinkhole_deleted.error_type} {sinkhole_deleted.message}")
|
logger.error(f"IPv6 Sinkhole Delete Error: {sinkhole_deleted.error_type} {sinkhole_deleted.message}")
|
||||||
|
|
||||||
terminate_tor_connection()
|
terminate_tor_connection()
|
||||||
|
|
||||||
# confirm firewall is off.
|
# confirm firewall is off.
|
||||||
if firewall_setting:
|
if firewall_setting:
|
||||||
check_and_kill_firewall() # on failure, this raises errors, which then bubble up
|
check_and_kill_firewall() # on failure, this raises errors, which then bubble up
|
||||||
|
# else:
|
||||||
|
# error_log = f"Error with deleting the connection: {delete_completed.error_type} {delete_completed.message}"
|
||||||
|
# logger.error(error_log)
|
||||||
|
# raise ConnectionTerminationError(f'The connection could not be terminated. {error_log}')
|
||||||
|
|
||||||
# finally end that profile's JSON state:
|
# finally end that profile's JSON state:
|
||||||
SystemState.dissolve()
|
if SystemStateController.exists():
|
||||||
|
SystemState.dissolve()
|
||||||
else:
|
return Result(valid=True)
|
||||||
error_log = f"Error with deleting the connection: {delete_completed.error_type} {delete_completed.message}"
|
|
||||||
logger.error(error_log)
|
|
||||||
raise ConnectionTerminationError(f'The connection could not be terminated. {error_log}')
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -115,45 +119,17 @@ def evaluate_location_and_internet(profile: SystemProfile, connection_observer:
|
||||||
|
|
||||||
return location_result
|
return location_result
|
||||||
|
|
||||||
def _cleanup_on_error(firewall_setting: bool, dns_setting: bool) -> None:
|
# def _cleanup_on_error(firewall_setting: bool, dns_setting: bool) -> None:
|
||||||
"""Safely terminate connection on error, swallowing cleanup errors."""
|
# """Safely terminate connection on error, swallowing cleanup errors."""
|
||||||
try:
|
# terminate_system_connection(firewall_setting, dns_setting)
|
||||||
terminate_system_connection(firewall_setting, dns_setting)
|
|
||||||
except ConnectionTerminationError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def _finish_success(final_result: Result, profile_id: int) -> Result:
|
def _finish_success(final_result: Result, profile_id: int) -> Result:
|
||||||
Notifications.emit(EventType.SYSTEM_ENABLE, profile_id)
|
|
||||||
notification = "Connected."
|
|
||||||
Notifications.emit(EventType.STATUS_UPDATE, notification)
|
|
||||||
|
|
||||||
terminate_tor_connection()
|
terminate_tor_connection()
|
||||||
time.sleep(0.3)
|
time.sleep(0.3)
|
||||||
return final_result
|
return final_result
|
||||||
|
|
||||||
|
|
||||||
def changed_mtu(profile: SystemProfile, current_mtu: Optional[int]) -> bool:
|
|
||||||
notification = "Testing MTU.."
|
|
||||||
Notifications.emit(EventType.STATUS_UPDATE, notification)
|
|
||||||
|
|
||||||
# optimal_mtu = loop_through_testing_mtu()
|
|
||||||
optimal_mtu = asyncio.run(loop_through_testing_mtu())
|
|
||||||
logger.info(f"Optimal MTU is {optimal_mtu}")
|
|
||||||
|
|
||||||
if optimal_mtu == current_mtu:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Assuming it isn't already at the max, there's no need to reset it to the max,
|
|
||||||
if current_mtu != MAX_MTU and optimal_mtu == MAX_MTU:
|
|
||||||
return False
|
|
||||||
|
|
||||||
notification = f"Updating WG MTU with {optimal_mtu}"
|
|
||||||
Notifications.emit(EventType.STATUS_UPDATE, notification)
|
|
||||||
return insert_mtu_into_config_on_disk(profile=profile, mtu=optimal_mtu)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def __establish_system_connection(
|
def __establish_system_connection(
|
||||||
profile: SystemProfile,
|
profile: SystemProfile,
|
||||||
firewall_setting: bool = False,
|
firewall_setting: bool = False,
|
||||||
|
|
@ -164,8 +140,6 @@ def __establish_system_connection(
|
||||||
Orchestrates the full flow of the connection.
|
Orchestrates the full flow of the connection.
|
||||||
|
|
||||||
Steps:
|
Steps:
|
||||||
1) Check Prereqs
|
|
||||||
2) Disable any pre-existing system connections
|
|
||||||
3) Turn on WG via nmcli
|
3) Turn on WG via nmcli
|
||||||
4) Turn on IPv6 sinkhole
|
4) Turn on IPv6 sinkhole
|
||||||
5) Setup State (JSON file)
|
5) Setup State (JSON file)
|
||||||
|
|
@ -173,17 +147,10 @@ def __establish_system_connection(
|
||||||
7) Set DNS (if enabled), and test it. Let's it slide even if failed.
|
7) Set DNS (if enabled), and test it. Let's it slide even if failed.
|
||||||
8) Testing, confirming, displaying to the end user.
|
8) Testing, confirming, displaying to the end user.
|
||||||
"""
|
"""
|
||||||
check_system_prereqs()
|
|
||||||
notification = "Terming existing connections.."
|
|
||||||
Notifications.emit(EventType.STATUS_UPDATE, notification)
|
|
||||||
logger.info(notification)
|
|
||||||
terminate_system_connection(firewall_setting, dns_setting)
|
|
||||||
|
|
||||||
# ============= INITIAL NMCLI CONNECTION =============
|
# ============= INITIAL NMCLI CONNECTION =============
|
||||||
notification = "Setting Up WireGuard.."
|
notification = "Setting Up WireGuard.."
|
||||||
Notifications.emit(EventType.STATUS_UPDATE, notification)
|
Notifications.emit(EventType.STATUS_UPDATE, notification)
|
||||||
# if connection_observer:
|
|
||||||
# connection_observer.notify("connecting", notification)
|
|
||||||
wg_config_path = profile.get_wireguard_configuration_path()
|
wg_config_path = profile.get_wireguard_configuration_path()
|
||||||
nmcli_result = setup_nmcli_connection(wg_config_path)
|
nmcli_result = setup_nmcli_connection(wg_config_path)
|
||||||
|
|
||||||
|
|
@ -204,17 +171,6 @@ def __establish_system_connection(
|
||||||
if not sinkhole_result:
|
if not sinkhole_result:
|
||||||
raise ConnectionError('IPv6 Could not be protected.')
|
raise ConnectionError('IPv6 Could not be protected.')
|
||||||
|
|
||||||
# ============= SETUP STATE =============
|
|
||||||
# Even if firewall is off, we want to save the fact we turned on the VPN tunnel, before we raise errors.
|
|
||||||
|
|
||||||
logger.info("Setting State JSON..")
|
|
||||||
current_state = SystemStateController.create(
|
|
||||||
profile_id=profile.id,
|
|
||||||
firewalled=firewall_setting, # intended setting, not result yet
|
|
||||||
dns_set=dns_setting, # intended setting, not result yet,
|
|
||||||
protocol=ProtocolChoice.WIREGUARD
|
|
||||||
)
|
|
||||||
|
|
||||||
# ============= PREP SETTINGS =============
|
# ============= PREP SETTINGS =============
|
||||||
if connection_observer:
|
if connection_observer:
|
||||||
connection_observer.notify("connecting", "Getting Interface Name..")
|
connection_observer.notify("connecting", "Getting Interface Name..")
|
||||||
|
|
@ -273,18 +229,28 @@ def __establish_system_connection(
|
||||||
if not test_result.valid:
|
if not test_result.valid:
|
||||||
notification = "Test Failed."
|
notification = "Test Failed."
|
||||||
Notifications.emit(EventType.STATUS_UPDATE, notification)
|
Notifications.emit(EventType.STATUS_UPDATE, notification)
|
||||||
|
logger.error("Returning that the test failed..")
|
||||||
return test_result
|
return test_result
|
||||||
|
|
||||||
# ============= UPDATE STATE =============
|
# ============= UPDATE STATE =============
|
||||||
if dns_setting and not did_dns_work:
|
# if dns_setting and not did_dns_work:
|
||||||
current_state.firewalled = firewall_tracker
|
# current_state.firewalled = firewall_tracker
|
||||||
current_state.dns_set = did_dns_work
|
# current_state.dns_set = did_dns_work
|
||||||
SystemStateController.update_or_create(current_state)
|
# SystemStateController.update_or_create(current_state)
|
||||||
notification = "DNS Set Did Not Work."
|
# notification = "DNS Set Did Not Work."
|
||||||
Notifications.emit(EventType.STATUS_UPDATE, notification)
|
# Notifications.emit(EventType.STATUS_UPDATE, notification)
|
||||||
|
|
||||||
|
# ============= SETUP STATE =============
|
||||||
|
logger.info("Setting State JSON..")
|
||||||
|
current_state = SystemStateController.create(
|
||||||
|
profile_id=profile.id,
|
||||||
|
firewalled=firewall_setting,
|
||||||
|
dns_set=did_dns_work,
|
||||||
|
protocol=ProtocolChoice.WIREGUARD
|
||||||
|
)
|
||||||
return Result(valid=True, data=current_state)
|
return Result(valid=True, data=current_state)
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
def establish_system_connection(
|
def establish_system_connection(
|
||||||
profile: SystemProfile,
|
profile: SystemProfile,
|
||||||
|
|
@ -296,11 +262,14 @@ def establish_system_connection(
|
||||||
Establish connection with a maximum of 2 retries.
|
Establish connection with a maximum of 2 retries.
|
||||||
|
|
||||||
Steps)
|
Steps)
|
||||||
1) Verify Endpoint's encryption
|
1) Check Prereqs
|
||||||
2) get settings
|
2) Verify Endpoint's encryption
|
||||||
3) Attempt 1 -> (Fail) -> Location Check
|
3) get settings
|
||||||
4) Attempt 2 -> (Fail) -> MTU Check -> Attempt 3
|
4) Disable any pre-existing system connections
|
||||||
"""
|
"""
|
||||||
|
# ================= PRE-REQS =================
|
||||||
|
check_system_prereqs()
|
||||||
|
|
||||||
# ================= VERIFY ENDPOINT =================
|
# ================= VERIFY ENDPOINT =================
|
||||||
if connection_observer:
|
if connection_observer:
|
||||||
connection_observer.notify("connecting", "Verifying endpoint..")
|
connection_observer.notify("connecting", "Verifying endpoint..")
|
||||||
|
|
@ -314,109 +283,152 @@ def establish_system_connection(
|
||||||
# ================= CONNECT WG ================
|
# ================= CONNECT WG ================
|
||||||
# We allow 3 total attempts (Initial + 2 retries)
|
# We allow 3 total attempts (Initial + 2 retries)
|
||||||
for attempt in range(1, 4):
|
for attempt in range(1, 4):
|
||||||
try:
|
# =================================
|
||||||
# if connection_observer:
|
# PRE-EXISTING
|
||||||
# connection_observer.notify("connecting", f"Connecting attempt {attempt} of 3.. ")
|
# =================================
|
||||||
|
logger.info("Terminating pre-existing")
|
||||||
|
terminated = terminate_system_connection(firewall_setting, dns_setting)
|
||||||
|
if terminated.valid:
|
||||||
|
logger.info("Terminated pre-existing")
|
||||||
|
else:
|
||||||
|
logger.error(f"Failed to terminate pre-existing {terminated.message}")
|
||||||
|
return terminated
|
||||||
|
|
||||||
notification = f"Connecting attempt {attempt} of 3.. "
|
# func_choice = partial(register_wireguard_session, profile=profile, connection_observer=connection_observer)
|
||||||
Notifications.emit(EventType.STATUS_UPDATE, notification)
|
# return Result(valid=False, function_choice=func_choice, error_type=ResultError.RENEGOTIATION, message="Would you like to try Wireguard key renegotiation? (Note: this invalidates other profiles with the same subscription.)")
|
||||||
|
|
||||||
# Attempt the actual system connection
|
print("sleeping")
|
||||||
connection_result = __establish_system_connection(
|
time.sleep(5)
|
||||||
profile=profile,
|
|
||||||
firewall_setting=firewall_setting,
|
notification = f"Connecting attempt {attempt}.. "
|
||||||
dns_setting=dns_setting,
|
Notifications.emit(EventType.STATUS_UPDATE, notification)
|
||||||
|
|
||||||
|
# =================================
|
||||||
|
# CONNECT
|
||||||
|
# =================================
|
||||||
|
# Attempt the actual system connection
|
||||||
|
connection_result = __establish_system_connection(
|
||||||
|
profile=profile,
|
||||||
|
firewall_setting=firewall_setting,
|
||||||
|
dns_setting=dns_setting,
|
||||||
|
connection_observer=connection_observer
|
||||||
|
)
|
||||||
|
|
||||||
|
print("sleeping")
|
||||||
|
time.sleep(5)
|
||||||
|
|
||||||
|
# =================================
|
||||||
|
# EVALUATE
|
||||||
|
# =================================
|
||||||
|
if connection_result.valid:
|
||||||
|
return _finish_success(final_result=connection_result, profile_id=profile.id)
|
||||||
|
|
||||||
|
# =================================
|
||||||
|
# ATTEMPT ONE: LOCATION UP?
|
||||||
|
# =================================
|
||||||
|
if attempt == 1:
|
||||||
|
# First failure: Evaluate Location and Internet without connection,
|
||||||
|
logger.info("Terminating system connection to check location")
|
||||||
|
terminate_system_connection(firewall_setting, dns_setting)
|
||||||
|
|
||||||
|
location_result = evaluate_location_and_internet(
|
||||||
|
profile=profile,
|
||||||
connection_observer=connection_observer
|
connection_observer=connection_observer
|
||||||
)
|
)
|
||||||
|
|
||||||
if connection_result.valid:
|
#============== NO RETRY #==============
|
||||||
return _finish_success(final_result=connection_result, profile_id=profile.id)
|
if location_result.backoff_strategy == BackoffStrategy.NO_RETRY:
|
||||||
|
logger.error(f"Location check failed: {location_result.message}")
|
||||||
# If not valid, we treat it as a failure to trigger the except block logic
|
bottom_left = "Node Down"
|
||||||
notification = f"{attempt} attempt failed.."
|
Notifications.emit(EventType.STATUS_UPDATE, bottom_left)
|
||||||
|
notification = location_result.message
|
||||||
|
Notifications.emit(EventType.SHOW_POPUP, notification)
|
||||||
|
return location_result
|
||||||
|
|
||||||
|
#============== NO SEEMS FINE #==============
|
||||||
|
notification = "Location check passed. Retrying immediately..."
|
||||||
Notifications.emit(EventType.STATUS_UPDATE, notification)
|
Notifications.emit(EventType.STATUS_UPDATE, notification)
|
||||||
raise ConnectionError(notification)
|
logger.info(notification)
|
||||||
|
continue # Proceed to attempt 2
|
||||||
|
|
||||||
except (ConnectionError, CalledProcessError) as e:
|
# =================================
|
||||||
|
# ATTEMPT 2+ MTU, PORT, WG KEY
|
||||||
|
# =================================
|
||||||
|
|
||||||
# single time port is offered forever.
|
# Connection is still On for MTU testing:
|
||||||
if port_was_changed_already_today():
|
|
||||||
# permanent config modify:
|
|
||||||
ConfigurationController.change_port_prompts(False)
|
|
||||||
|
|
||||||
if attempt == 1:
|
# =================================
|
||||||
# First failure: Evaluate Location and Internet without connection,
|
# MTU
|
||||||
_cleanup_on_error(firewall_setting, dns_setting)
|
# =================================
|
||||||
|
should_test_mtu, current_mtu = should_we_even_test_mtu(profile.id)
|
||||||
location_result = evaluate_location_and_internet(
|
|
||||||
profile=profile,
|
|
||||||
connection_observer=connection_observer
|
|
||||||
)
|
|
||||||
|
|
||||||
if location_result.backoff_strategy == BackoffStrategy.NO_RETRY:
|
|
||||||
logger.error(f"Location check failed: {location_result.message}")
|
|
||||||
bottom_left = "Node Down"
|
|
||||||
Notifications.emit(EventType.STATUS_UPDATE, bottom_left)
|
|
||||||
notification = location_result.message
|
|
||||||
Notifications.emit(EventType.SHOW_POPUP, notification)
|
|
||||||
return location_result
|
|
||||||
# raise ConnectionError(location_result.message) from e
|
|
||||||
|
|
||||||
notification = "Location check passed. Retrying immediately..."
|
|
||||||
Notifications.emit(EventType.STATUS_UPDATE, notification)
|
|
||||||
logger.info(notification)
|
|
||||||
continue # Proceed to attempt 2
|
|
||||||
|
|
||||||
elif attempt == 2:
|
|
||||||
# Second failure: Evaluate MTU WITH connection on
|
|
||||||
current_mtu = get_mtu(profile_id=profile.id)
|
|
||||||
|
|
||||||
if current_mtu != MAX_MTU or not current_mtu:
|
|
||||||
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.")
|
|
||||||
# return Result(valid=False, error_type=ResultError.CONNECTION, message="Failed to connect.")
|
|
||||||
|
|
||||||
if not current_mtu or current_mtu > MIN_MTU:
|
|
||||||
notification = "Testing MTU..."
|
|
||||||
Notifications.emit(EventType.STATUS_UPDATE, notification)
|
|
||||||
logger.info(notification)
|
|
||||||
if mtu_is_fine(current_mtu):
|
|
||||||
notification = "MTU is NOT the issue."
|
|
||||||
Notifications.emit(EventType.STATUS_UPDATE, notification)
|
|
||||||
logger.info(notification)
|
|
||||||
# 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.")
|
|
||||||
# return Result(valid=False, error_type=ResultError.CONNECTION, message="Failed to connect.")
|
|
||||||
|
|
||||||
if changed_mtu(profile=profile, current_mtu=current_mtu):
|
|
||||||
logger.info("MTU changed. Retrying connection...")
|
|
||||||
continue # Proceed to attempt 3
|
|
||||||
else:
|
|
||||||
logger.warning("MTU check performed but no change was made or user rejected it.")
|
|
||||||
notification = "MTU was NOT changed."
|
|
||||||
Notifications.emit(EventType.STATUS_UPDATE, notification)
|
|
||||||
else:
|
|
||||||
logger.info("Skipping MTU testing, it's not relevant here.")
|
|
||||||
|
|
||||||
# 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."
|
|
||||||
Notifications.emit(EventType.STATUS_UPDATE, notification)
|
|
||||||
_cleanup_on_error(firewall_setting, dns_setting)
|
|
||||||
raise ConnectionError("Failed to connect.")
|
|
||||||
|
|
||||||
|
if should_test_mtu:
|
||||||
|
logger.info("We should test (and potentially change) the MTU..")
|
||||||
|
if was_mtu_changed(profile=profile, current_mtu=current_mtu):
|
||||||
|
logger.info("MTU was changed, so we're trying again.")
|
||||||
|
continue
|
||||||
else:
|
else:
|
||||||
# Attempt 3 failed
|
logger.info("MTU was tested but NOT changed.")
|
||||||
logger.error("Maximum retries reached. Connection failed.")
|
else:
|
||||||
notification = "Failed to connect."
|
logger.info("MTU was NOT tested.")
|
||||||
Notifications.emit(EventType.STATUS_UPDATE, notification)
|
|
||||||
_cleanup_on_error(firewall_setting, dns_setting)
|
# Take down connection for the rest of the tests:
|
||||||
raise ConnectionError(notification)
|
terminated = terminate_system_connection(firewall_setting, dns_setting)
|
||||||
# return Result(valid=False, error_type=ResultError.CONNECTION, message="Failed to connect.")
|
if terminated.valid:
|
||||||
|
logger.info("Took down the system connection for the other tests.")
|
||||||
|
else:
|
||||||
|
return terminated
|
||||||
|
|
||||||
|
# =================================
|
||||||
|
# PORT
|
||||||
|
# =================================
|
||||||
|
if should_we_try_to_change_port():
|
||||||
|
logger.info("We should try to change port")
|
||||||
|
|
||||||
|
# Offer the choice to the end user:
|
||||||
|
return offer_port_change(profile)
|
||||||
|
else:
|
||||||
|
logger.info("Skipping port choice as an offer.")
|
||||||
|
|
||||||
|
# =================================
|
||||||
|
# WIREGUARD RENEGOTIATION
|
||||||
|
# =================================
|
||||||
|
terminate_system_connection(firewall_setting, dns_setting)
|
||||||
|
func_choice = partial(register_wireguard_session, profile=profile, connection_observer=connection_observer)
|
||||||
|
return Result(valid=False, function_choice=func_choice, error_type=ResultError.RENEGOTIATION, message="Would you like to try Wireguard key renegotiation? (Note: this invalidates other profiles with the same subscription.)")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# except (ConnectionError, CalledProcessError) as e:
|
||||||
|
|
||||||
|
# # single time port is offered forever.
|
||||||
|
# if port_was_changed_already_today():
|
||||||
|
# # permanent config modify:
|
||||||
|
# ConfigurationController.change_port_prompts(False)
|
||||||
|
|
||||||
|
# else:
|
||||||
|
# notification = "Failed to connect."
|
||||||
|
# Notifications.emit(EventType.STATUS_UPDATE, notification)
|
||||||
|
# _cleanup_on_error(firewall_setting, dns_setting)
|
||||||
|
# raise ConnectionError("Failed to connect.")
|
||||||
|
|
||||||
|
# else:
|
||||||
|
# # Attempt 3 failed
|
||||||
|
# logger.error("Maximum retries reached. Connection failed.")
|
||||||
|
# notification = "Failed to connect."
|
||||||
|
# Notifications.emit(EventType.STATUS_UPDATE, notification)
|
||||||
|
# _cleanup_on_error(firewall_setting, dns_setting)
|
||||||
|
# raise ConnectionError(notification)
|
||||||
|
# # return Result(valid=False, error_type=ResultError.CONNECTION, message="Failed to connect.")
|
||||||
|
|
||||||
|
# First take
|
||||||
|
# _cleanup_on_error(firewall_setting, dns_setting)
|
||||||
|
# _cleanup_on_error(firewall_setting, dns_setting)
|
||||||
|
# # return Result(valid=False, error_type=ResultError.CONNECTION, message="Failed to connect.")
|
||||||
|
|
|
||||||
|
|
@ -156,18 +156,22 @@ def enable_profile(
|
||||||
connection_result = establish_connection(profile, ignore=ignore, connection_observer=connection_observer)
|
connection_result = establish_connection(profile, ignore=ignore, connection_observer=connection_observer)
|
||||||
if not connection_result:
|
if not connection_result:
|
||||||
logger.error("Connection result is empty.")
|
logger.error("Connection result is empty.")
|
||||||
notification = "Connection Failed"
|
|
||||||
Notifications.emit(EventType.STATUS_UPDATE, notification)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
if not connection_result.valid:
|
logger.info(f"Exiting establish connection with {connection_result}")
|
||||||
|
|
||||||
|
if connection_result.valid:
|
||||||
|
# if profile_observer is not None:
|
||||||
|
# profile_observer.notify('enabled', profile)
|
||||||
|
|
||||||
|
Notifications.emit(EventType.SYSTEM_ENABLE, profile.id)
|
||||||
|
notification = "Connected."
|
||||||
|
Notifications.emit(EventType.STATUS_UPDATE, notification)
|
||||||
|
return Result(valid=True, data=profile.type)
|
||||||
|
|
||||||
|
else:
|
||||||
return connection_result
|
return connection_result
|
||||||
|
|
||||||
if profile_observer is not None:
|
|
||||||
profile_observer.notify('enabled', profile)
|
|
||||||
notification = "Enabled."
|
|
||||||
Notifications.emit(EventType.STATUS_UPDATE, notification)
|
|
||||||
return Result(valid=True, data=profile.type)
|
|
||||||
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:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue