diff --git a/core/controllers/ConnectionController.py b/core/controllers/ConnectionController.py index 086829d..3e218c1 100644 --- a/core/controllers/ConnectionController.py +++ b/core/controllers/ConnectionController.py @@ -5,7 +5,8 @@ from core.services.networking.general_connection_tools.testing_evaluating import from core.services.networking.systemwide.systemwide_wireguard import establish_system_connection, terminate_system_connection from core.services.keys_and_verifications.endpoint_verification import verify_wireguard_endpoint from core.observers.TicketObserver import TicketObserver - +from core.services.networking.systemwide.general_tools.test_any_protocol import test_session_ip +from core.models.enum_models import ProtocolChoice from collections.abc import Callable from core.Constants import Constants @@ -79,8 +80,6 @@ class ConnectionController: else: from core.services.profiles.profile_connections import disable_profile disable_profile(profile=profile, connection_observer=connection_observer, ticket_observer=ticket_observer) - # from core.controllers.ProfileController import ProfileController - # ProfileController.disable(profile=profile, connection_observer=connection_observer, ticket_observer=ticket_observer) if profile.connection.code == 'tor': @@ -107,14 +106,28 @@ class ConnectionController: ConnectionController.establish_proxy_session_connection(profile, session_directory, port_number, proxy_port_number) session_state.network_port_numbers.proxy.append(proxy_port_number) - if not profile.connection.is_unprotected(): + # if not profile.connection.is_unprotected(): + + if profile.connection.code == 'wireguard': + test_result = test_session_ip(port=port_number, profile_id=profile.id, protocol=ProtocolChoice.WIREGUARD, connection_observer=connection_observer) + if test_result.valid: + logger.info("Connection established.") + if connection_observer: + connection_observer.notify("connected", "Confirmed") + else: + logger.error(test_result.message) + raise ConnectionError(test_result.message) + + elif profile.connection.code == 'tor': await_connection(proxy_port_number or port_number, connection_observer=connection_observer) + SessionStateController.update_or_create(session_state) return proxy_port_number or port_number + @staticmethod def establish_tor_connection(connection_observer: Optional[ConnectionObserver] = None): try: diff --git a/core/services/networking/general_connection_tools/config_tools.py b/core/services/networking/general_connection_tools/config_tools.py index bf76071..70f5802 100644 --- a/core/services/networking/general_connection_tools/config_tools.py +++ b/core/services/networking/general_connection_tools/config_tools.py @@ -1,21 +1,36 @@ +from core.models.BaseProfile import ProfileType from core.Constants import Constants -import os +from core.errors.logger import logger -def extract_endpoint_ip(profile_id: str) -> str | bool: - try: - profile_path = Constants.HV_PROFILE_CONFIG_HOME + f'/{profile_id}' +import os +from typing import Optional + +def extract_endpoint_ip(profile_id: str, profile_type: Optional[ProfileType] = None) -> str | bool: + + profile_path = Constants.HV_PROFILE_CONFIG_HOME + f'/{profile_id}' + + if not profile_type: # assumes systemwide: wg_conf_path = f'{profile_path}/wg.conf.bak' - print(f"wg_conf_path is {wg_conf_path}") - if not os.path.exists(wg_conf_path): - return None + elif profile_type == ProfileType.SESSION: + wg_conf_path = f'{profile_path}/wg.conf' + else: + return None + + if not os.path.exists(wg_conf_path): + logger.error(f"Path doesn't exist: {wg_conf_path}") + return None + + try: with open(wg_conf_path, 'r') as f: content = f.read() for line in content.split('\n'): if line.strip().startswith('Endpoint = '): endpoint = line.strip().split(' = ')[1] return endpoint.split(':')[0] + logger.error("Found no content.") return None - except Exception: + except Exception as e: + logger.error(f"Unable to parse WG config: {str(e)}") return None def extract_dns(profile_id: str) -> str | bool: diff --git a/core/services/networking/general_connection_tools/ip.py b/core/services/networking/general_connection_tools/ip.py index 22003ab..35aac28 100644 --- a/core/services/networking/general_connection_tools/ip.py +++ b/core/services/networking/general_connection_tools/ip.py @@ -4,7 +4,6 @@ from core.services.networking.httpx.classify_response import classify_response from core.models.Result import Result, ResultError from core.services.networking.api_requests.ApiResponseModel import ApiResponse, ErrorType -# errors/loggers/observers from core.Constants import Constants from core.errors.logger import logger from essentials.observers.ConnectionObserver import ConnectionObserver @@ -13,6 +12,10 @@ from essentials.observers.ConnectionObserver import ConnectionObserver import time import random import httpx +from httpx_socks import AsyncProxyTransport +import httpx_socks +from httpx_socks import ProxyType + from typing import Optional API_ENDPOINT_FORMATS = { @@ -26,7 +29,7 @@ API_ENDPOINT_FORMATS = { MAX_ATTEMPTS = 3 -def test(connection_observer: Optional[ConnectionObserver]) -> Result: +def test(connection_observer: Optional[ConnectionObserver], port: Optional[int] = None) -> Result: """ Cycles through 3 possible endpoints, to attempt to get an IP address for the systemwide connection. @@ -35,7 +38,16 @@ def test(connection_observer: Optional[ConnectionObserver]) -> Result: endpoint_index = 0 endpoint_list = list(API_ENDPOINT_FORMATS.keys()) - _ip_test_client = httpx.Client(http2=True, timeout=6) + if port: + transport = httpx_socks.SyncProxyTransport( + proxy_type=ProxyType.SOCKS5, + proxy_host="127.0.0.1", + proxy_port=port, + ) + _ip_test_client = httpx.Client(transport=transport, http2=True, timeout=6) + else: + _ip_test_client = httpx.Client(http2=True, timeout=6) + while attempt < MAX_ATTEMPTS: endpoint_url = endpoint_list[attempt] diff --git a/core/services/networking/systemwide/general_tools/test_any_protocol.py b/core/services/networking/systemwide/general_tools/test_any_protocol.py index 9897e37..2bd64a1 100644 --- a/core/services/networking/systemwide/general_tools/test_any_protocol.py +++ b/core/services/networking/systemwide/general_tools/test_any_protocol.py @@ -2,6 +2,7 @@ from core.services.networking.general_connection_tools.config_tools import extract_endpoint_ip from core.services.networking.general_connection_tools import ip from essentials.observers.ConnectionObserver import ConnectionObserver +from core.models.BaseProfile import ProfileType from core.models.enum_models import ProtocolChoice from core.utils.basic_operations.write_or_read_from_json import get_value_from_json_file from core.models.Result import Result, ResultError @@ -11,6 +12,29 @@ from core.Constants import Constants import os +def test_session_ip(port: int, profile_id: int, protocol: ProtocolChoice, connection_observer: ConnectionObserver) -> Result: + """Compare the real vs expected IP address.""" + + if protocol == ProtocolChoice.WIREGUARD: + expected_ip_address = extract_endpoint_ip(profile_id=profile_id, profile_type=ProfileType.SESSION) + else: + error_msg = "Not currently supported protocol for test_session_ip" + logger.error(error_msg) + return Result(valid=False, error_type=ResultError.INVALID_INPUT, message=error_msg) + + if not expected_ip_address: + error_msg = "Could not get the expected IP address from local files." + logger.error(error_msg) + return Result(valid=False, error_type=ResultError.MISSING_DATA, message=error_msg) + + # reality: + logger.info(f"testing on port {port}") + ip_result = ip.test(connection_observer=connection_observer, port=port) + + return compare_ips(ip_result=ip_result, expected_ip_address=expected_ip_address) + + + def get_singbox_ip(profile_id: int) -> str|None: """Get the IP address for a Singbox based profile based on local disk.""" config_path = f'{Constants.HV_SYSTEM_PROFILE_CONFIG_PATH}/{profile_id}/proxy.json' @@ -26,9 +50,6 @@ def get_singbox_ip(profile_id: int) -> str|None: def test_ip_systemwide_for_any_protocol(profile_id: int, protocol: ProtocolChoice, connection_observer: ConnectionObserver) -> Result: """Compare the real vs expected IP address.""" - # protocol = profile.type - # profile_id = profile.id - # what's expected: if protocol == ProtocolChoice.WIREGUARD: expected_ip_address = extract_endpoint_ip(profile_id) @@ -45,6 +66,12 @@ def test_ip_systemwide_for_any_protocol(profile_id: int, protocol: ProtocolChoic # reality: ip_result = ip.test(connection_observer) + return compare_ips(ip_result=ip_result, expected_ip_address=expected_ip_address) + + +def compare_ips(ip_result: Result, expected_ip_address: str) -> Result: + """Compare the expected to the real IP address.""" + if not ip_result.valid: logger.error(ip_result.message) return ip_result # result object diff --git a/core/services/networking/systemwide/systemwide_wireguard.py b/core/services/networking/systemwide/systemwide_wireguard.py index 58ce16f..e987292 100644 --- a/core/services/networking/systemwide/systemwide_wireguard.py +++ b/core/services/networking/systemwide/systemwide_wireguard.py @@ -5,6 +5,8 @@ from core.services.keys_and_verifications.endpoint_verification import verify_wi from core.models.Result import Result, ResultError from core.services.networking.systemwide.systemwide_utils import extract_wg_interface_name, check_system_prereqs 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.general_connection_tools.config_tools import extract_endpoint_ip, extract_dns from core.services.networking.systemwide import killswitch @@ -198,13 +200,16 @@ def __establish_system_connection( # ============= TESTING ============= logger.info("If we made it this far, we have nmcli on, ipv6 sinkhole on, firewall on.") - try: - if connection_observer: - connection_observer.notify("connecting", "Testing Connection Health..") - await_connection(connection_observer=connection_observer) + if connection_observer: + connection_observer.notify("connecting", "Testing Connection Health..") - except ConnectionError: - raise ConnectionError('The connection could not be established.') + test_result = test_ip_systemwide_for_any_protocol( + profile_id=profile.id, + protocol=ProtocolChoice.WIREGUARD, + connection_observer=connection_observer + ) + if not test_result.valid: + return test_result # ============= UPDATE STATE ============= if dns_setting and not did_dns_work: @@ -212,6 +217,9 @@ def __establish_system_connection( current_state.dns_set = did_dns_work SystemStateController.update_or_create(current_state) + if connection_observer: + connection_observer.notify("connected", "Confirmed") + Notifications.emit(EventType.SYSTEM_ENABLE, profile.id) return Result(valid=True, data=current_state) @@ -221,6 +229,7 @@ def evaluate_connection_result(connection_result: Result) -> Result: """In the future, this will raise other error types or filter replies.""" if not connection_result.valid: logger.error(f"[SYSTEMWIDE WG] Critical issue, could not establish a connection: {connection_result.message}") + time.sleep(1) raise ConnectionError(f"Connection validation failed: {connection_result.message}") else: return connection_result @@ -309,5 +318,5 @@ def establish_system_connection( raise terminate_tor_connection() - time.sleep(1.0) + time.sleep(0.3) return final_result \ No newline at end of file diff --git a/core/services/payment_phase/ticket_cycle.py b/core/services/payment_phase/ticket_cycle.py index 8ce3d7a..85601d5 100644 --- a/core/services/payment_phase/ticket_cycle.py +++ b/core/services/payment_phase/ticket_cycle.py @@ -7,6 +7,7 @@ from core.Constants import Constants from core.models.Result import Result, ResultError from core.errors.logger import logger + def wipe_ticket_cycle() -> Result: """Wipe the entire global ticketing cycle & all associated tickets."""