Compare commits
5 commits
00d6fc7738
...
2ccbdba06b
| Author | SHA1 | Date | |
|---|---|---|---|
| 2ccbdba06b | |||
| 9740e0f53b | |||
| fd7949cea4 | |||
| fe3b7ad59e | |||
| 703fe57c12 |
14 changed files with 923 additions and 299 deletions
|
|
@ -1,8 +1,21 @@
|
|||
# Major Change Log:
|
||||
|
||||
|
||||
# 2.4.2
|
||||
### Surgery on ConnectionController
|
||||
Huge separation of ConnectionController & ProfileController into naked function modules for enabling a connection, registering wireguard keys, coordinating proxy configs, subscriptions, endpoint verification, and the logistical flow. Additionally enums were used for type checking, instead of polymorphic functions. This version is stable and confirmed working for systemwide and session wireguard, but not yet proxies.
|
||||
<br/>
|
||||
|
||||
# 2.4.1
|
||||
### Enums for Profile types.
|
||||
July 18, 2026
|
||||
Introduce Base profile types on load, which gradually will switch over on save/edits. Transition Connections to using Enum instead of raw strings. End goal here is to transition ConnectionController to use the Enums.
|
||||
<br/>
|
||||
|
||||
# 2.4.0
|
||||
July 15, 2026
|
||||
Robust Database Error Handling & Migrations System. For New Client upgrades (Operational Errors) & Old Clients with New JSON keys from an API (TypeErrors). We also added an SQL generic handling wrapper for use across any project or function.
|
||||
<br/>
|
||||
|
||||
# 2.3.9
|
||||
July 11, 2026
|
||||
|
|
|
|||
|
|
@ -1,13 +1,19 @@
|
|||
from core.errors.exceptions import *
|
||||
from core.errors.logger import logger
|
||||
|
||||
from core.services.networking.general_connection_tools.testing_evaluating import await_connection, system_uses_wireguard_interface, await_network_interface
|
||||
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 collections.abc import Callable
|
||||
from core.Constants import Constants
|
||||
from core.Errors import InvalidSubscriptionError, MissingSubscriptionError, ConnectionUnprotectedError, ConnectionTerminationError, CommandNotFoundError
|
||||
from core.controllers.ConfigurationController import ConfigurationController
|
||||
from core.controllers.ProfileController import ProfileController
|
||||
# from core.controllers.ProfileController import ProfileController
|
||||
from core.controllers.SessionStateController import SessionStateController
|
||||
from core.controllers.SystemStateController import SystemStateController
|
||||
from core.models.BaseProfile import ProfileType
|
||||
from core.models.session.SessionProfile import SessionProfile
|
||||
from core.models.system.SystemProfile import SystemProfile
|
||||
from core.models.system.SystemState import SystemState
|
||||
|
|
@ -20,12 +26,15 @@ from subprocess import CalledProcessError
|
|||
from typing import Union, Optional, Any
|
||||
import os
|
||||
import random
|
||||
import re
|
||||
|
||||
# import re
|
||||
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
# import sys
|
||||
import tempfile
|
||||
import time
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class ConnectionController:
|
||||
|
|
@ -51,85 +60,87 @@ class ConnectionController:
|
|||
else:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def establish_connection(profile: Union[SessionProfile, SystemProfile], ignore: tuple[type[Exception]] = (), connection_observer: Optional[ConnectionObserver] = None):
|
||||
# @staticmethod
|
||||
# def establish_connection(profile: Union[SessionProfile, SystemProfile], ignore: tuple[type[Exception]] = (), connection_observer: Optional[ConnectionObserver] = None):
|
||||
|
||||
connection = profile.connection
|
||||
# connection = profile.connection
|
||||
|
||||
# needs_proxy_configuration comes from the child connection models
|
||||
# for the system it returns false blindly. for the session is checks if the connection type is masked.
|
||||
if connection.needs_proxy_configuration() and not profile.has_proxy_configuration():
|
||||
# # needs_proxy_configuration comes from the child connection models
|
||||
# # for the system it returns false blindly. for the session is checks if the connection type is masked.
|
||||
# # if connection.masked and not profile.has_proxy_configuration():
|
||||
# if connection.needs_proxy_configuration() and not profile.has_proxy_configuration():
|
||||
|
||||
if profile.has_subscription():
|
||||
# if profile.has_subscription():
|
||||
|
||||
if not profile.subscription.has_been_activated():
|
||||
ProfileController.activate_subscription(profile, connection_observer=connection_observer)
|
||||
# if not profile.subscription.has_been_activated():
|
||||
# ProfileController.activate_subscription(profile, connection_observer=connection_observer)
|
||||
|
||||
proxy_configuration = ConnectionController.with_preferred_connection(profile.subscription.billing_code, task=WebServiceApiService.get_proxy_configuration, connection_observer=connection_observer)
|
||||
# proxy_configuration = ConnectionController.with_preferred_connection(profile.subscription.billing_code, task=WebServiceApiService.get_proxy_configuration, connection_observer=connection_observer)
|
||||
|
||||
if proxy_configuration is None:
|
||||
raise InvalidSubscriptionError()
|
||||
# if proxy_configuration is None:
|
||||
# raise InvalidSubscriptionError()
|
||||
|
||||
profile.attach_proxy_configuration(proxy_configuration)
|
||||
# profile.attach_proxy_configuration(proxy_configuration)
|
||||
|
||||
else:
|
||||
raise MissingSubscriptionError()
|
||||
# else:
|
||||
# raise MissingSubscriptionError()
|
||||
|
||||
# The needs_wireguard_configuration comes from the connection model, and can be transitioned to get it directly from the object's data
|
||||
# The has_wireguard_configuration comes from each polymorph object doing an os check on if the wg config exists
|
||||
if connection.needs_wireguard_configuration() and not profile.has_wireguard_configuration():
|
||||
# # The needs_wireguard_configuration comes from the connection model,
|
||||
# # and can be transitioned to get it directly from the object's data
|
||||
# # The has_wireguard_configuration comes from each polymorph object doing an os check on if the wg config exists
|
||||
# if connection.needs_wireguard_configuration() and not profile.has_wireguard_configuration():
|
||||
|
||||
if profile.has_subscription():
|
||||
# if profile.has_subscription():
|
||||
|
||||
if not profile.subscription.has_been_activated():
|
||||
ProfileController.activate_subscription(profile, connection_observer=connection_observer)
|
||||
# if not profile.subscription.has_been_activated():
|
||||
# ProfileController.activate_subscription(profile, connection_observer=connection_observer)
|
||||
|
||||
ProfileController.register_wireguard_session(profile, connection_observer=connection_observer)
|
||||
# ProfileController.register_wireguard_session(profile, connection_observer=connection_observer)
|
||||
|
||||
else:
|
||||
# else:
|
||||
|
||||
if profile.is_system_profile():
|
||||
# if profile.is_system_profile():
|
||||
|
||||
if ConnectionController.system_uses_wireguard_interface() and SystemStateController.exists():
|
||||
# if system_uses_wireguard_interface() and SystemStateController.exists():
|
||||
|
||||
try:
|
||||
ConnectionController.terminate_system_connection()
|
||||
except ConnectionTerminationError:
|
||||
pass
|
||||
# try:
|
||||
# terminate_system_connection()
|
||||
# except ConnectionTerminationError:
|
||||
# pass
|
||||
|
||||
raise MissingSubscriptionError()
|
||||
# raise MissingSubscriptionError()
|
||||
|
||||
if profile.is_session_profile():
|
||||
# if profile.is_session_profile():
|
||||
|
||||
try:
|
||||
return ConnectionController.establish_session_connection(profile, ignore=ignore, connection_observer=connection_observer)
|
||||
# try:
|
||||
# return ConnectionController.establish_session_connection(profile, ignore=ignore, connection_observer=connection_observer)
|
||||
|
||||
except ConnectionError:
|
||||
# except ConnectionError:
|
||||
|
||||
if ConnectionController.__should_renegotiate(profile):
|
||||
# if ConnectionController.__should_renegotiate(profile):
|
||||
|
||||
ProfileController.register_wireguard_session(profile, connection_observer=connection_observer)
|
||||
return ConnectionController.establish_session_connection(profile, ignore=ignore, connection_observer=connection_observer)
|
||||
# ProfileController.register_wireguard_session(profile, connection_observer=connection_observer)
|
||||
# return ConnectionController.establish_session_connection(profile, ignore=ignore, connection_observer=connection_observer)
|
||||
|
||||
else:
|
||||
raise ConnectionError('The connection could not be established.')
|
||||
# else:
|
||||
# raise ConnectionError('The connection could not be established.')
|
||||
|
||||
if profile.is_system_profile():
|
||||
# if profile.is_system_profile():
|
||||
|
||||
try:
|
||||
return ConnectionController.establish_system_connection(profile, ignore=ignore, connection_observer=connection_observer)
|
||||
# try:
|
||||
# return establish_system_connection(profile, ignore=ignore, connection_observer=connection_observer)
|
||||
|
||||
except ConnectionError:
|
||||
# except ConnectionError:
|
||||
|
||||
if ConnectionController.__should_renegotiate(profile):
|
||||
# if ConnectionController.__should_renegotiate(profile):
|
||||
|
||||
ProfileController.register_wireguard_session(profile, connection_observer=connection_observer)
|
||||
return ConnectionController.establish_system_connection(profile, ignore=ignore, connection_observer=connection_observer)
|
||||
# ProfileController.register_wireguard_session(profile, connection_observer=connection_observer)
|
||||
# return establish_system_connection(profile, ignore=ignore, connection_observer=connection_observer)
|
||||
|
||||
else:
|
||||
raise ConnectionError('The connection could not be established.')
|
||||
# else:
|
||||
# raise ConnectionError('The connection could not be established.')
|
||||
|
||||
return None
|
||||
# return None
|
||||
|
||||
@staticmethod
|
||||
def establish_session_connection(profile: SessionProfile, ignore: tuple[type[Exception]] = (), connection_observer: Optional[ConnectionObserver] = None):
|
||||
|
|
@ -143,11 +154,12 @@ class ConnectionController:
|
|||
# this is a check from SessionConnection of if there's a systemwide with mask
|
||||
if profile.connection.is_unprotected():
|
||||
|
||||
if not ConnectionController.system_uses_wireguard_interface():
|
||||
if not system_uses_wireguard_interface():
|
||||
|
||||
if not ConnectionUnprotectedError in ignore:
|
||||
raise ConnectionUnprotectedError('Connection unprotected while the system is not using a WireGuard interface.')
|
||||
else:
|
||||
from core.controllers.ProfileController import ProfileController
|
||||
ProfileController.disable(profile)
|
||||
|
||||
if profile.connection.code == 'tor':
|
||||
|
|
@ -160,7 +172,7 @@ class ConnectionController:
|
|||
elif profile.connection.code == 'wireguard':
|
||||
|
||||
if ConfigurationController.get_endpoint_verification_enabled():
|
||||
ProfileController.verify_wireguard_endpoint(profile, ignore=ignore)
|
||||
verify_wireguard_endpoint(profile, ignore=ignore)
|
||||
|
||||
port_number = ConnectionService.get_random_available_port_number()
|
||||
ConnectionController.establish_wireguard_session_connection(profile, session_directory, port_number)
|
||||
|
|
@ -175,57 +187,51 @@ class ConnectionController:
|
|||
session_state.network_port_numbers.proxy.append(proxy_port_number)
|
||||
|
||||
if not profile.connection.is_unprotected():
|
||||
ConnectionController.await_connection(proxy_port_number or port_number, connection_observer=connection_observer)
|
||||
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_system_connection(profile: SystemProfile, ignore: tuple[type[Exception]] = (), connection_observer: Optional[ConnectionObserver] = None):
|
||||
# @staticmethod
|
||||
# def establish_system_connection(profile: SystemProfile, ignore: tuple[type[Exception]] = (), connection_observer: Optional[ConnectionObserver] = None):
|
||||
|
||||
if ConfigurationController.get_endpoint_verification_enabled():
|
||||
ProfileController.verify_wireguard_endpoint(profile, ignore=ignore)
|
||||
# if ConfigurationController.get_endpoint_verification_enabled():
|
||||
# ProfileController.verify_wireguard_endpoint(profile, ignore=ignore)
|
||||
|
||||
try:
|
||||
ConnectionController.__establish_system_connection(profile, connection_observer)
|
||||
# try:
|
||||
# ConnectionController.__establish_system_connection(profile, connection_observer)
|
||||
|
||||
except ConnectionError:
|
||||
# except ConnectionError:
|
||||
|
||||
try:
|
||||
ConnectionController.terminate_system_connection()
|
||||
except ConnectionTerminationError:
|
||||
pass
|
||||
# try:
|
||||
# ConnectionController.terminate_system_connection()
|
||||
# except ConnectionTerminationError:
|
||||
# pass
|
||||
|
||||
raise ConnectionError('The connection could not be established.')
|
||||
# raise ConnectionError('The connection could not be established.')
|
||||
|
||||
except CalledProcessError:
|
||||
# except CalledProcessError:
|
||||
|
||||
try:
|
||||
ConnectionController.terminate_system_connection()
|
||||
except ConnectionTerminationError:
|
||||
pass
|
||||
# try:
|
||||
# ConnectionController.terminate_system_connection()
|
||||
# except ConnectionTerminationError:
|
||||
# pass
|
||||
|
||||
try:
|
||||
ConnectionController.__establish_system_connection(profile, connection_observer)
|
||||
# try:
|
||||
# ConnectionController.__establish_system_connection(profile, connection_observer)
|
||||
|
||||
except (ConnectionError, CalledProcessError):
|
||||
# except (ConnectionError, CalledProcessError):
|
||||
|
||||
try:
|
||||
ConnectionController.terminate_system_connection()
|
||||
except ConnectionTerminationError:
|
||||
pass
|
||||
# try:
|
||||
# ConnectionController.terminate_system_connection()
|
||||
# except ConnectionTerminationError:
|
||||
# pass
|
||||
|
||||
raise ConnectionError('The connection could not be established.')
|
||||
# raise ConnectionError('The connection could not be established.')
|
||||
|
||||
ConnectionController.terminate_tor_connection()
|
||||
time.sleep(1.0)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def test_observer(connection_observer: Optional[ConnectionObserver] = None):
|
||||
if connection_observer is not None:
|
||||
connection_observer.notify('custom_message', "HELLO")
|
||||
# ConnectionController.terminate_tor_connection()
|
||||
# time.sleep(1.0)
|
||||
|
||||
|
||||
@staticmethod
|
||||
|
|
@ -242,11 +248,11 @@ class ConnectionController:
|
|||
if connection_observer is not None:
|
||||
connection_observer.notify('custom_message', "Tor Can't Initialize")
|
||||
|
||||
@staticmethod
|
||||
def terminate_tor_connection():
|
||||
# @staticmethod
|
||||
# def terminate_tor_connection():
|
||||
|
||||
tor_module = TorModule(Constants.HV_TOR_STATE_HOME)
|
||||
tor_module.stop_service()
|
||||
# tor_module = TorModule(Constants.HV_TOR_STATE_HOME)
|
||||
# tor_module.stop_service()
|
||||
|
||||
@staticmethod
|
||||
def establish_tor_session_connection(port_number: int, connection_observer: Optional[ConnectionObserver] = None):
|
||||
|
|
@ -320,26 +326,28 @@ class ConnectionController:
|
|||
|
||||
return subprocess.Popen(('proxychains4', '-f', proxychains_configuration_file_path, 'microsocks', '-p', str(proxy_port_number)), stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
|
||||
|
||||
@staticmethod
|
||||
def terminate_system_connection():
|
||||
# @staticmethod
|
||||
# def terminate_system_connection():
|
||||
|
||||
if shutil.which('nmcli') is None:
|
||||
raise CommandNotFoundError('nmcli')
|
||||
# if shutil.which('nmcli') is None:
|
||||
# raise CommandNotFoundError('nmcli')
|
||||
|
||||
if SystemStateController.exists():
|
||||
# if SystemStateController.exists():
|
||||
|
||||
process = subprocess.Popen(('nmcli', 'connection', 'delete', 'wg'), stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
|
||||
completed_successfully = not bool(os.waitpid(process.pid, 0)[1] >> 8)
|
||||
# process = subprocess.Popen(('nmcli', 'connection', 'delete', 'wg'), stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
|
||||
# completed_successfully = not bool(os.waitpid(process.pid, 0)[1] >> 8)
|
||||
|
||||
if completed_successfully or not ConnectionController.system_uses_wireguard_interface():
|
||||
# if completed_successfully or not ConnectionController.system_uses_wireguard_interface():
|
||||
|
||||
subprocess.run(('nmcli', 'connection', 'delete', 'hv-ipv6-sink'), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
ConnectionController.terminate_tor_connection()
|
||||
SystemState.dissolve()
|
||||
# subprocess.run(('nmcli', 'connection', 'delete', 'hv-ipv6-sink'), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
# ConnectionController.terminate_tor_connection()
|
||||
# SystemState.dissolve()
|
||||
|
||||
else:
|
||||
raise ConnectionTerminationError('The connection could not be terminated.')
|
||||
# else:
|
||||
# raise ConnectionTerminationError('The connection could not be terminated.')
|
||||
|
||||
|
||||
# Stays here for Tor based only. Will move with Tor later.
|
||||
@staticmethod
|
||||
def get_proxies(port_number: int):
|
||||
|
||||
|
|
@ -348,107 +356,107 @@ class ConnectionController:
|
|||
https=f'socks5h://127.0.0.1:{port_number}'
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def await_connection(port_number: Optional[int] = None, connection_observer: Optional[ConnectionObserver] = None):
|
||||
# @staticmethod
|
||||
# def await_connection(port_number: Optional[int] = None, connection_observer: Optional[ConnectionObserver] = None):
|
||||
|
||||
if port_number is None:
|
||||
ConnectionController.await_network_interface()
|
||||
# if port_number is None:
|
||||
# ConnectionController.await_network_interface()
|
||||
|
||||
for retry_count in range(Constants.MAX_CONNECTION_ATTEMPTS):
|
||||
# for retry_count in range(Constants.MAX_CONNECTION_ATTEMPTS):
|
||||
|
||||
if connection_observer is not None:
|
||||
# if connection_observer is not None:
|
||||
|
||||
connection_observer.notify('connecting', dict(
|
||||
retry_interval=Constants.CONNECTION_RETRY_INTERVAL,
|
||||
maximum_number_of_attempts=Constants.MAX_CONNECTION_ATTEMPTS,
|
||||
attempt_count=retry_count + 1
|
||||
))
|
||||
# connection_observer.notify('connecting', dict(
|
||||
# retry_interval=Constants.CONNECTION_RETRY_INTERVAL,
|
||||
# maximum_number_of_attempts=Constants.MAX_CONNECTION_ATTEMPTS,
|
||||
# attempt_count=retry_count + 1
|
||||
# ))
|
||||
|
||||
try:
|
||||
# try:
|
||||
|
||||
ConnectionController.__test_connection(port_number)
|
||||
return
|
||||
# ConnectionController.__test_connection(port_number)
|
||||
# return
|
||||
|
||||
except ConnectionError:
|
||||
# except ConnectionError:
|
||||
|
||||
time.sleep(Constants.CONNECTION_RETRY_INTERVAL)
|
||||
retry_count += 1
|
||||
# time.sleep(Constants.CONNECTION_RETRY_INTERVAL)
|
||||
# retry_count += 1
|
||||
|
||||
raise ConnectionError('The connection could not be established.')
|
||||
# raise ConnectionError('The connection could not be established.')
|
||||
|
||||
@staticmethod
|
||||
def await_network_interface():
|
||||
# @staticmethod
|
||||
# def await_network_interface():
|
||||
|
||||
network_interface_is_activated = False
|
||||
# network_interface_is_activated = False
|
||||
|
||||
retry_interval = .5
|
||||
maximum_number_of_attempts = 10
|
||||
attempt = 0
|
||||
# retry_interval = .5
|
||||
# maximum_number_of_attempts = 10
|
||||
# attempt = 0
|
||||
|
||||
while not network_interface_is_activated and attempt < maximum_number_of_attempts:
|
||||
# while not network_interface_is_activated and attempt < maximum_number_of_attempts:
|
||||
|
||||
time.sleep(retry_interval)
|
||||
# time.sleep(retry_interval)
|
||||
|
||||
network_interface_is_activated = ConnectionController.system_uses_wireguard_interface()
|
||||
attempt += 1
|
||||
# network_interface_is_activated = ConnectionController.system_uses_wireguard_interface()
|
||||
# attempt += 1
|
||||
|
||||
if not network_interface_is_activated:
|
||||
raise ConnectionError('The network interface could not be activated.')
|
||||
# if not network_interface_is_activated:
|
||||
# raise ConnectionError('The network interface could not be activated.')
|
||||
|
||||
@staticmethod
|
||||
def system_uses_wireguard_interface():
|
||||
# @staticmethod
|
||||
# def system_uses_wireguard_interface():
|
||||
|
||||
if shutil.which('ip') is None:
|
||||
raise CommandNotFoundError('ip')
|
||||
# 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())
|
||||
# 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)))
|
||||
# return bool(re.search('dev wg', str(process_output)))
|
||||
|
||||
@staticmethod
|
||||
def __establish_system_connection(profile: SystemProfile, connection_observer: Optional[ConnectionObserver] = None):
|
||||
# @staticmethod
|
||||
# def __establish_system_connection(profile: SystemProfile, connection_observer: Optional[ConnectionObserver] = None):
|
||||
|
||||
if shutil.which('dbus-send') is None:
|
||||
raise CommandNotFoundError('dbus-send')
|
||||
# if shutil.which('dbus-send') is None:
|
||||
# raise CommandNotFoundError('dbus-send')
|
||||
|
||||
if shutil.which('nmcli') is None:
|
||||
raise CommandNotFoundError('nmcli')
|
||||
# if shutil.which('nmcli') is None:
|
||||
# raise CommandNotFoundError('nmcli')
|
||||
|
||||
ConnectionController.terminate_system_connection()
|
||||
# ConnectionController.terminate_system_connection()
|
||||
|
||||
try:
|
||||
process_output = subprocess.check_output(('nmcli', 'connection', 'import', '--temporary', 'type', 'wireguard', 'file', profile.get_wireguard_configuration_path()), text=True)
|
||||
except CalledProcessError:
|
||||
raise ConnectionError('The connection could not be established.')
|
||||
# try:
|
||||
# process_output = subprocess.check_output(('nmcli', 'connection', 'import', '--temporary', 'type', 'wireguard', 'file', profile.get_wireguard_configuration_path()), text=True)
|
||||
# except CalledProcessError:
|
||||
# raise ConnectionError('The connection could not be established.')
|
||||
|
||||
try:
|
||||
# try:
|
||||
|
||||
connection_id = (m := re.search(r'(?<=\()([a-f0-9-]+?)(?=\))', process_output)) and m.group(1)
|
||||
ipv6_method = subprocess.check_output(('nmcli', '-g', 'ipv6.method', 'connection', 'show', connection_id), text=True).strip()
|
||||
# connection_id = (m := re.search(r'(?<=\()([a-f0-9-]+?)(?=\))', process_output)) and m.group(1)
|
||||
# ipv6_method = subprocess.check_output(('nmcli', '-g', 'ipv6.method', 'connection', 'show', connection_id), text=True).strip()
|
||||
|
||||
except CalledProcessError:
|
||||
raise ConnectionError('The connection could not be established.')
|
||||
# except CalledProcessError:
|
||||
# raise ConnectionError('The connection could not be established.')
|
||||
|
||||
if ipv6_method in ('disabled', 'ignore'):
|
||||
# if ipv6_method in ('disabled', 'ignore'):
|
||||
|
||||
try:
|
||||
subprocess.run(('dbus-send', '--system', '--print-reply', '--dest=org.freedesktop.NetworkManager', '/org/freedesktop/NetworkManager', 'org.freedesktop.DBus.Properties.Set', 'string:org.freedesktop.NetworkManager', 'string:ConnectivityCheckEnabled', 'variant:boolean:false'), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True)
|
||||
except CalledProcessError:
|
||||
raise ConnectionError('The connection could not be established.')
|
||||
# try:
|
||||
# subprocess.run(('dbus-send', '--system', '--print-reply', '--dest=org.freedesktop.NetworkManager', '/org/freedesktop/NetworkManager', 'org.freedesktop.DBus.Properties.Set', 'string:org.freedesktop.NetworkManager', 'string:ConnectivityCheckEnabled', 'variant:boolean:false'), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True)
|
||||
# except CalledProcessError:
|
||||
# raise ConnectionError('The connection could not be established.')
|
||||
|
||||
try:
|
||||
subprocess.run(('nmcli', 'connection', 'add', 'type', 'dummy', 'save', 'no', 'con-name', 'hv-ipv6-sink', 'ifname', 'hvipv6sink0', 'ipv6.method', 'manual', 'ipv6.addresses', 'fd7a:fd4b:54e3:077c::/64', 'ipv6.gateway', 'fd7a:fd4b:54e3:077c::1', 'ipv6.dns', '::1', 'ipv6.route-metric', '72'), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True)
|
||||
except CalledProcessError:
|
||||
raise ConnectionError('The connection could not be established.')
|
||||
# try:
|
||||
# subprocess.run(('nmcli', 'connection', 'add', 'type', 'dummy', 'save', 'no', 'con-name', 'hv-ipv6-sink', 'ifname', 'hvipv6sink0', 'ipv6.method', 'manual', 'ipv6.addresses', 'fd7a:fd4b:54e3:077c::/64', 'ipv6.gateway', 'fd7a:fd4b:54e3:077c::1', 'ipv6.dns', '::1', 'ipv6.route-metric', '72'), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True)
|
||||
# except CalledProcessError:
|
||||
# raise ConnectionError('The connection could not be established.')
|
||||
|
||||
SystemStateController.create(profile.id)
|
||||
# SystemStateController.create(profile.id)
|
||||
|
||||
try:
|
||||
ConnectionController.await_connection(connection_observer=connection_observer)
|
||||
# try:
|
||||
# ConnectionController.await_connection(connection_observer=connection_observer)
|
||||
|
||||
except ConnectionError:
|
||||
raise ConnectionError('The connection could not be established.')
|
||||
# except ConnectionError:
|
||||
# raise ConnectionError('The connection could not be established.')
|
||||
|
||||
@staticmethod
|
||||
def __with_tor_connection(*args, task: Callable[..., Any], connection_observer: Optional[ConnectionObserver] = None, **kwargs):
|
||||
|
|
@ -456,62 +464,62 @@ class ConnectionController:
|
|||
port_number = ConnectionService.get_random_available_port_number()
|
||||
ConnectionController.establish_tor_session_connection(port_number, connection_observer=connection_observer)
|
||||
|
||||
ConnectionController.await_connection(port_number, connection_observer=connection_observer)
|
||||
await_connection(port_number, connection_observer=connection_observer)
|
||||
task_output = task(*args, proxies=ConnectionController.get_proxies(port_number), **kwargs)
|
||||
|
||||
ConnectionController.terminate_tor_session_connection(port_number)
|
||||
|
||||
return task_output
|
||||
|
||||
@staticmethod
|
||||
def __test_connection(port_number: Optional[int] = None, timeout: float = 4.0):
|
||||
# @staticmethod
|
||||
# def __test_connection(port_number: Optional[int] = None, timeout: float = 4.0):
|
||||
|
||||
request_urls = [Constants.PING_URL]
|
||||
proxies = None
|
||||
# request_urls = [Constants.PING_URL]
|
||||
# proxies = None
|
||||
|
||||
if os.environ.get('PING_URL') is None:
|
||||
# if os.environ.get('PING_URL') is None:
|
||||
|
||||
request_urls.extend([
|
||||
'https://hc1.simplifiedprivacy.net',
|
||||
'https://hc2.simplifiedprivacy.org',
|
||||
'https://hc3.hydraveil.net'
|
||||
])
|
||||
# request_urls.extend([
|
||||
# 'https://hc1.simplifiedprivacy.net',
|
||||
# 'https://hc2.simplifiedprivacy.org',
|
||||
# 'https://hc3.hydraveil.net'
|
||||
# ])
|
||||
|
||||
random.shuffle(request_urls)
|
||||
# random.shuffle(request_urls)
|
||||
|
||||
if port_number is not None:
|
||||
proxies = ConnectionController.get_proxies(port_number)
|
||||
# if port_number is not None:
|
||||
# proxies = ConnectionController.get_proxies(port_number)
|
||||
|
||||
for request_url in request_urls:
|
||||
# for request_url in request_urls:
|
||||
|
||||
command = [
|
||||
sys.executable, '-u', '-c', 'import requests, sys\n'
|
||||
'try:\n'
|
||||
f' response = requests.get(\'{request_url}\', proxies={proxies}, timeout={timeout})\n'
|
||||
' response.raise_for_status(); print(response.text)\n'
|
||||
'except requests.exceptions.RequestException:\n'
|
||||
' sys.exit(1)'
|
||||
]
|
||||
# command = [
|
||||
# sys.executable, '-u', '-c', 'import requests, sys\n'
|
||||
# 'try:\n'
|
||||
# f' response = requests.get(\'{request_url}\', proxies={proxies}, timeout={timeout})\n'
|
||||
# ' response.raise_for_status(); print(response.text)\n'
|
||||
# 'except requests.exceptions.RequestException:\n'
|
||||
# ' sys.exit(1)'
|
||||
# ]
|
||||
|
||||
try:
|
||||
# try:
|
||||
|
||||
_response = subprocess.check_output(command, text=True, timeout=timeout)
|
||||
return None
|
||||
# _response = subprocess.check_output(command, text=True, timeout=timeout)
|
||||
# return None
|
||||
|
||||
except (subprocess.CalledProcessError, subprocess.TimeoutExpired):
|
||||
pass
|
||||
# except (subprocess.CalledProcessError, subprocess.TimeoutExpired):
|
||||
# pass
|
||||
|
||||
raise ConnectionError('The connection could not be established.')
|
||||
# raise ConnectionError('The connection could not be established.')
|
||||
|
||||
@staticmethod
|
||||
def __should_renegotiate(profile: Union[SessionProfile, SystemProfile]):
|
||||
# @staticmethod
|
||||
# def __should_renegotiate(profile: Union[SessionProfile, SystemProfile]):
|
||||
|
||||
if not profile.has_subscription():
|
||||
raise MissingSubscriptionError()
|
||||
# if not profile.has_subscription():
|
||||
# raise MissingSubscriptionError()
|
||||
|
||||
if profile.connection.needs_wireguard_configuration() and profile.has_wireguard_configuration():
|
||||
# if profile.connection.needs_wireguard_configuration() and profile.has_wireguard_configuration():
|
||||
|
||||
if profile.subscription.has_been_activated():
|
||||
return True
|
||||
# if profile.subscription.has_been_activated():
|
||||
# return True
|
||||
|
||||
return False
|
||||
# return False
|
||||
|
|
@ -1,3 +1,7 @@
|
|||
from core.services.networking.systemwide.systemwide_wireguard import terminate_system_connection
|
||||
from core.services.networking.general_connection_tools.testing_evaluating import system_uses_wireguard_interface
|
||||
from core.services.networking.general_connection_tools.connection_enable import establish_connection
|
||||
|
||||
from core.Errors import InvalidSubscriptionError, MissingSubscriptionError, ConnectionTerminationError, ProfileActivationError, ProfileDeactivationError, MissingLocationError, ConnectionUnprotectedError, EndpointVerificationError, ProfileStateConflictError
|
||||
from core.controllers.ApplicationController import ApplicationController
|
||||
from core.controllers.ApplicationVersionController import ApplicationVersionController
|
||||
|
|
@ -37,20 +41,31 @@ class ProfileController:
|
|||
|
||||
|
||||
@staticmethod
|
||||
def enable(profile: Union[SessionProfile, SystemProfile], ignore: tuple[type[Exception]] = (), pristine: bool = False, asynchronous: bool = False, profile_observer: ProfileObserver = None, application_version_observer: ApplicationVersionObserver = None, connection_observer: ConnectionObserver = None):
|
||||
def enable(
|
||||
profile: Union[SessionProfile, SystemProfile],
|
||||
ignore: tuple[type[Exception]] = (),
|
||||
pristine: bool = False,
|
||||
asynchronous: bool = False,
|
||||
profile_observer: ProfileObserver = None,
|
||||
application_version_observer: ApplicationVersionObserver = None,
|
||||
connection_observer: ConnectionObserver = None):
|
||||
|
||||
from core.controllers.ConnectionController import ConnectionController
|
||||
|
||||
# =========== ALREADY ENABLED ============
|
||||
if ProfileController.is_enabled(profile):
|
||||
|
||||
if not ProfileStateConflictError in ignore:
|
||||
raise ProfileStateConflictError('The profile is already enabled or its session was not properly terminated.')
|
||||
else:
|
||||
ProfileController.disable(profile)
|
||||
|
||||
# =========== PRISTINE ============
|
||||
if pristine:
|
||||
profile.delete_data()
|
||||
|
||||
# ============================================================================
|
||||
# SESSION
|
||||
# ============================================================================
|
||||
if profile.is_session_profile():
|
||||
|
||||
application_version = profile.application_version
|
||||
|
|
@ -59,7 +74,7 @@ class ProfileController:
|
|||
ApplicationVersionController.install(application_version, application_version_observer=application_version_observer, connection_observer=connection_observer)
|
||||
|
||||
try:
|
||||
port_number = ConnectionController.establish_connection(profile, ignore=ignore, connection_observer=connection_observer)
|
||||
port_number = establish_connection(profile, ignore=ignore, connection_observer=connection_observer)
|
||||
except ConnectionError:
|
||||
raise ProfileActivationError('The profile could not be enabled.')
|
||||
|
||||
|
|
@ -68,10 +83,12 @@ class ProfileController:
|
|||
|
||||
ApplicationController.launch(application_version, profile, port_number, asynchronous=asynchronous, profile_observer=profile_observer)
|
||||
|
||||
# ============================================================================
|
||||
# SYSTEMWIDE
|
||||
# ============================================================================
|
||||
if profile.is_system_profile():
|
||||
|
||||
try:
|
||||
ConnectionController.establish_connection(profile, ignore=ignore, connection_observer=connection_observer)
|
||||
establish_connection(profile, ignore=ignore, connection_observer=connection_observer)
|
||||
except ConnectionError:
|
||||
raise ProfileActivationError('The profile could not be enabled.')
|
||||
|
||||
|
|
@ -115,7 +132,7 @@ class ProfileController:
|
|||
raise ProfileDeactivationError('The profile could not be disabled.')
|
||||
|
||||
try:
|
||||
ConnectionController.terminate_system_connection()
|
||||
terminate_system_connection()
|
||||
except ConnectionTerminationError:
|
||||
raise ProfileDeactivationError('The profile could not be disabled.')
|
||||
|
||||
|
|
@ -177,7 +194,7 @@ class ProfileController:
|
|||
system_state = SystemStateController.get()
|
||||
|
||||
if system_state is not None and system_state.profile_id is profile.id:
|
||||
return ConnectionController.system_uses_wireguard_interface()
|
||||
return system_uses_wireguard_interface()
|
||||
|
||||
return False
|
||||
|
||||
|
|
@ -211,28 +228,28 @@ class ProfileController:
|
|||
def has_proxy_configuration(profile: Union[SessionProfile, SystemProfile]):
|
||||
profile.has_proxy_configuration()
|
||||
|
||||
@staticmethod
|
||||
def register_wireguard_session(profile: Union[SessionProfile, SystemProfile], connection_observer: Optional[ConnectionObserver] = None):
|
||||
# @staticmethod
|
||||
# def register_wireguard_session(profile: Union[SessionProfile, SystemProfile], connection_observer: Optional[ConnectionObserver] = None):
|
||||
|
||||
from core.controllers.ConnectionController import ConnectionController
|
||||
# from core.controllers.ConnectionController import ConnectionController
|
||||
|
||||
if not profile.has_subscription():
|
||||
raise MissingSubscriptionError()
|
||||
# if not profile.has_subscription():
|
||||
# raise MissingSubscriptionError()
|
||||
|
||||
if not profile.has_location():
|
||||
raise MissingLocationError()
|
||||
# if not profile.has_location():
|
||||
# raise MissingLocationError()
|
||||
|
||||
wireguard_keys = ProfileController.__generate_wireguard_keys()
|
||||
# wireguard_keys = ProfileController.__generate_wireguard_keys()
|
||||
|
||||
wireguard_configuration = ConnectionController.with_preferred_connection(profile.location.country_code, profile.location.code, profile.subscription.billing_code, wireguard_keys.get('public'), task=WebServiceApiService.post_wireguard_session, connection_observer=connection_observer)
|
||||
# wireguard_configuration = ConnectionController.with_preferred_connection(profile.location.country_code, profile.location.code, profile.subscription.billing_code, wireguard_keys.get('public'), task=WebServiceApiService.post_wireguard_session, connection_observer=connection_observer)
|
||||
|
||||
if wireguard_configuration is None:
|
||||
raise InvalidSubscriptionError()
|
||||
# if wireguard_configuration is None:
|
||||
# raise InvalidSubscriptionError()
|
||||
|
||||
expression = re.compile(r'^(PrivateKey =)\s?$', re.MULTILINE)
|
||||
wireguard_configuration = re.sub(expression, r'\1 ' + wireguard_keys.get('private'), wireguard_configuration)
|
||||
# expression = re.compile(r'^(PrivateKey =)\s?$', re.MULTILINE)
|
||||
# wireguard_configuration = re.sub(expression, r'\1 ' + wireguard_keys.get('private'), wireguard_configuration)
|
||||
|
||||
profile.attach_wireguard_configuration(wireguard_configuration)
|
||||
# profile.attach_wireguard_configuration(wireguard_configuration)
|
||||
|
||||
@staticmethod
|
||||
def get_wireguard_configuration_path(profile: Union[SessionProfile, SystemProfile]):
|
||||
|
|
@ -242,65 +259,65 @@ class ProfileController:
|
|||
def has_wireguard_configuration(profile: Union[SessionProfile, SystemProfile]):
|
||||
return profile.has_wireguard_configuration()
|
||||
|
||||
@staticmethod
|
||||
def verify_wireguard_endpoint(profile: Union[SessionProfile, SystemProfile], ignore: tuple[type[Exception]] = ()):
|
||||
# @staticmethod
|
||||
# def verify_wireguard_endpoint(profile: Union[SessionProfile, SystemProfile], ignore: tuple[type[Exception]] = ()):
|
||||
|
||||
try:
|
||||
ProfileController.__verify_wireguard_endpoint(profile)
|
||||
# try:
|
||||
# ProfileController.__verify_wireguard_endpoint(profile)
|
||||
|
||||
except EndpointVerificationError as error:
|
||||
# except EndpointVerificationError as error:
|
||||
|
||||
if not EndpointVerificationError in ignore:
|
||||
# if not EndpointVerificationError in ignore:
|
||||
|
||||
profile.address_security_incident()
|
||||
raise error
|
||||
# profile.address_security_incident()
|
||||
# raise error
|
||||
|
||||
@staticmethod
|
||||
def __verify_wireguard_endpoint(profile: Union[SessionProfile, SystemProfile]):
|
||||
# @staticmethod
|
||||
# def __verify_wireguard_endpoint(profile: Union[SessionProfile, SystemProfile]):
|
||||
|
||||
from cryptography.hazmat.primitives.asymmetric import ed25519
|
||||
import base64
|
||||
# from cryptography.hazmat.primitives.asymmetric import ed25519
|
||||
# import base64
|
||||
|
||||
signature = profile.get_wireguard_configuration_metadata('Signature')
|
||||
wireguard_public_keys = profile.get_wireguard_public_keys()
|
||||
operator = profile.location.operator
|
||||
# signature = profile.get_wireguard_configuration_metadata('Signature')
|
||||
# wireguard_public_keys = profile.get_wireguard_public_keys()
|
||||
# operator = profile.location.operator
|
||||
|
||||
if signature is None:
|
||||
raise EndpointVerificationError('The WireGuard endpoint\'s signature could not be determined.')
|
||||
# if signature is None:
|
||||
# raise EndpointVerificationError('The WireGuard endpoint\'s signature could not be determined.')
|
||||
|
||||
if not wireguard_public_keys:
|
||||
raise EndpointVerificationError('The WireGuard endpoint\'s public key could not be determined.')
|
||||
# if not wireguard_public_keys:
|
||||
# raise EndpointVerificationError('The WireGuard endpoint\'s public key could not be determined.')
|
||||
|
||||
if operator is None:
|
||||
raise EndpointVerificationError('The WireGuard endpoint\'s operator could not be determined.')
|
||||
# if operator is None:
|
||||
# raise EndpointVerificationError('The WireGuard endpoint\'s operator could not be determined.')
|
||||
|
||||
try:
|
||||
# try:
|
||||
|
||||
operator_public_key = ed25519.Ed25519PublicKey.from_public_bytes(bytes.fromhex(operator.public_key))
|
||||
# operator_public_key = ed25519.Ed25519PublicKey.from_public_bytes(bytes.fromhex(operator.public_key))
|
||||
|
||||
for wireguard_public_key in wireguard_public_keys:
|
||||
operator_public_key.verify(base64.b64decode(signature), wireguard_public_key.encode('utf-8'))
|
||||
# for wireguard_public_key in wireguard_public_keys:
|
||||
# operator_public_key.verify(base64.b64decode(signature), wireguard_public_key.encode('utf-8'))
|
||||
|
||||
except Exception:
|
||||
raise EndpointVerificationError('The WireGuard endpoint could not be verified.')
|
||||
# except Exception:
|
||||
# raise EndpointVerificationError('The WireGuard endpoint could not be verified.')
|
||||
|
||||
@staticmethod
|
||||
def __generate_wireguard_keys():
|
||||
# @staticmethod
|
||||
# def __generate_wireguard_keys():
|
||||
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
|
||||
# from cryptography.hazmat.primitives import serialization
|
||||
# from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
|
||||
|
||||
raw_private_key = X25519PrivateKey.generate()
|
||||
# raw_private_key = X25519PrivateKey.generate()
|
||||
|
||||
public_key = raw_private_key.public_key().public_bytes(
|
||||
encoding=serialization.Encoding.Raw, format=serialization.PublicFormat.Raw
|
||||
)
|
||||
# public_key = raw_private_key.public_key().public_bytes(
|
||||
# encoding=serialization.Encoding.Raw, format=serialization.PublicFormat.Raw
|
||||
# )
|
||||
|
||||
private_key = raw_private_key.private_bytes(
|
||||
encoding=serialization.Encoding.Raw, format=serialization.PrivateFormat.Raw, encryption_algorithm=serialization.NoEncryption()
|
||||
)
|
||||
# private_key = raw_private_key.private_bytes(
|
||||
# encoding=serialization.Encoding.Raw, format=serialization.PrivateFormat.Raw, encryption_algorithm=serialization.NoEncryption()
|
||||
# )
|
||||
|
||||
return dict(
|
||||
private=base64.b64encode(private_key).decode(),
|
||||
public=base64.b64encode(public_key).decode()
|
||||
)
|
||||
# return dict(
|
||||
# private=base64.b64encode(private_key).decode(),
|
||||
# public=base64.b64encode(public_key).decode()
|
||||
# )
|
||||
|
|
|
|||
|
|
@ -1,19 +1,21 @@
|
|||
from dataclasses import dataclass
|
||||
from dataclasses_json import dataclass_json
|
||||
|
||||
|
||||
@dataclass_json
|
||||
@dataclass
|
||||
class BaseConnection:
|
||||
code: str
|
||||
|
||||
# called by: connection controller for basic type checks on wireguard code
|
||||
# Called by: ConnectionController
|
||||
# it uses it for basic type checks on wireguard code
|
||||
def needs_wireguard_configuration(self):
|
||||
return self.code == 'wireguard'
|
||||
|
||||
# Called By SubscriptionPlan
|
||||
def is_session_connection(self):
|
||||
return type(self).__name__ == 'SessionConnection'
|
||||
|
||||
# Not called. Dead code
|
||||
def is_system_connection(self):
|
||||
return type(self).__name__ == 'SystemConnection'
|
||||
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import re
|
|||
import shutil
|
||||
import tempfile
|
||||
from sqlalchemy.orm import Session
|
||||
from enum import Enum
|
||||
|
||||
@safe_db_operation
|
||||
def execute_location_sql(country_code: str, city_code: str, session: Session) -> DatabaseOperation:
|
||||
|
|
@ -51,6 +52,9 @@ def get_profile_location_data(country_code: str, city_code: str) -> Location:
|
|||
return None
|
||||
|
||||
|
||||
class ProfileType(str, Enum):
|
||||
SESSION = "session"
|
||||
SYSTEM = "system"
|
||||
|
||||
@dataclass_json
|
||||
@dataclass
|
||||
|
|
@ -61,6 +65,7 @@ class BaseProfile(ABC):
|
|||
name: str
|
||||
subscription: Optional[Subscription]
|
||||
location: Optional[Location] = field(metadata=config(exclude=Exclude.ALWAYS)) # SQLAlchemy object
|
||||
type: ProfileType
|
||||
|
||||
# legacy version included it to be serialized, but now it's an SQLAlchemy object.
|
||||
# location: Optional[Location]
|
||||
|
|
@ -237,16 +242,9 @@ class BaseProfile(ABC):
|
|||
# this needs error handling if there's no location or malconformed config.
|
||||
|
||||
|
||||
# legacy phased out since SQLAlchemy handles the time_zone.
|
||||
|
||||
# if location is not None:
|
||||
|
||||
# if profile['location'].get('time_zone') is not None:
|
||||
# location.time_zone = profile['location']['time_zone']
|
||||
|
||||
# profile['location'] = location
|
||||
|
||||
# =========== SESSION ===========
|
||||
if 'application_version' in profile:
|
||||
profile['type'] = ProfileType.SESSION
|
||||
|
||||
if profile['application_version'] is not None:
|
||||
application_version = ApplicationVersion.find(profile['application_version']['application_code'] or None, profile['application_version']['version_number'] or None)
|
||||
|
|
@ -258,7 +256,10 @@ class BaseProfile(ABC):
|
|||
# noinspection PyUnresolvedReferences
|
||||
profile = SessionProfile.from_dict(profile)
|
||||
|
||||
|
||||
# =========== SYSTEM ===========
|
||||
else:
|
||||
profile['type'] = ProfileType.SYSTEM
|
||||
|
||||
from core.models.system.SystemProfile import SystemProfile
|
||||
# noinspection PyUnresolvedReferences
|
||||
|
|
@ -299,3 +300,10 @@ class BaseProfile(ABC):
|
|||
@staticmethod
|
||||
def __get_data_path(id: int):
|
||||
return f'{Constants.HV_PROFILE_DATA_HOME}/{str(id)}'
|
||||
|
||||
|
||||
# legacy phased out since SQLAlchemy handles the time_zone.
|
||||
# if location is not None:
|
||||
# if profile['location'].get('time_zone') is not None:
|
||||
# location.time_zone = profile['location']['time_zone']
|
||||
# profile['location'] = location
|
||||
|
|
|
|||
|
|
@ -1,20 +1,32 @@
|
|||
from core.models.BaseConnection import BaseConnection
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
|
||||
class SessionConnectionTypes(str, Enum):
|
||||
WIREGUARD = "wireguard"
|
||||
TOR = "tor"
|
||||
|
||||
@dataclass
|
||||
class SessionConnection(BaseConnection):
|
||||
masked: bool = False
|
||||
code: SessionConnectionTypes
|
||||
masked: bool
|
||||
|
||||
def __post_init__(self):
|
||||
|
||||
if self.code not in ('system', 'tor', 'wireguard'):
|
||||
raise ValueError('Invalid connection code.')
|
||||
|
||||
# called by connection controller
|
||||
# Called by: ConnectionController
|
||||
# doesn't even make sense, it's checking if the Session is code system. this will always be false.
|
||||
def is_unprotected(self):
|
||||
return self.code == 'system' and self.masked is False
|
||||
|
||||
# Called by: SessionProfile.determine_timezone
|
||||
def needs_proxy_configuration(self):
|
||||
return self.masked is True
|
||||
|
||||
|
||||
# legacy:
|
||||
# @dataclass
|
||||
# class SessionConnection(BaseConnection):
|
||||
# masked: bool = False
|
||||
|
||||
# def __post_init__(self):
|
||||
|
||||
# if self.code not in ('system', 'tor', 'wireguard'):
|
||||
# raise ValueError('Invalid connection code.')
|
||||
|
|
|
|||
|
|
@ -1,16 +1,28 @@
|
|||
from core.models.BaseConnection import BaseConnection
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from typing import Literal
|
||||
|
||||
class SystemConnectionTypes(str, Enum):
|
||||
WIREGUARD = "wireguard"
|
||||
HYSTERIA2 = "hysteria2"
|
||||
VLESS = "vless"
|
||||
|
||||
@dataclass
|
||||
class SystemConnection (BaseConnection):
|
||||
|
||||
|
||||
def __post_init__(self):
|
||||
|
||||
if self.code not in ('vless', 'hysteria2', 'wireguard'):
|
||||
raise ValueError('Invalid connection code.')
|
||||
class SystemConnection(BaseConnection):
|
||||
code: SystemConnectionTypes
|
||||
masked: Literal[False] = False
|
||||
|
||||
@staticmethod
|
||||
def needs_proxy_configuration():
|
||||
return False
|
||||
|
||||
# legacy:
|
||||
# @dataclass
|
||||
# class SystemConnection (BaseConnection):
|
||||
|
||||
|
||||
# def __post_init__(self):
|
||||
|
||||
# if self.code not in ('vless', 'hysteria2', 'wireguard'):
|
||||
# raise ValueError('Invalid connection code.')
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
from core.models.session.SessionProfile import SessionProfile
|
||||
from core.models.system.SystemProfile import SystemProfile
|
||||
from core.models.BaseProfile import BaseProfile as Profile
|
||||
from core.Errors import InvalidSubscriptionError, MissingSubscriptionError, ConnectionTerminationError, ProfileActivationError, ProfileDeactivationError, MissingLocationError, ConnectionUnprotectedError, EndpointVerificationError, ProfileStateConflictError
|
||||
from typing import Union, Optional
|
||||
import base64
|
||||
|
||||
|
||||
def verify_wireguard_endpoint(profile: Union[SessionProfile, SystemProfile], ignore: tuple[type[Exception]] = ()):
|
||||
|
||||
try:
|
||||
__verify_wireguard_endpoint(profile)
|
||||
|
||||
except EndpointVerificationError as error:
|
||||
|
||||
if not EndpointVerificationError in ignore:
|
||||
|
||||
profile.address_security_incident()
|
||||
raise error
|
||||
|
||||
def __verify_wireguard_endpoint(profile: Union[SessionProfile, SystemProfile]):
|
||||
|
||||
from cryptography.hazmat.primitives.asymmetric import ed25519
|
||||
import base64
|
||||
|
||||
signature = profile.get_wireguard_configuration_metadata('Signature')
|
||||
wireguard_public_keys = profile.get_wireguard_public_keys()
|
||||
operator = profile.location.operator
|
||||
|
||||
if signature is None:
|
||||
raise EndpointVerificationError('The WireGuard endpoint\'s signature could not be determined.')
|
||||
|
||||
if not wireguard_public_keys:
|
||||
raise EndpointVerificationError('The WireGuard endpoint\'s public key could not be determined.')
|
||||
|
||||
if operator is None:
|
||||
raise EndpointVerificationError('The WireGuard endpoint\'s operator could not be determined.')
|
||||
|
||||
try:
|
||||
|
||||
operator_public_key = ed25519.Ed25519PublicKey.from_public_bytes(bytes.fromhex(operator.public_key))
|
||||
|
||||
for wireguard_public_key in wireguard_public_keys:
|
||||
operator_public_key.verify(base64.b64decode(signature), wireguard_public_key.encode('utf-8'))
|
||||
|
||||
except Exception:
|
||||
raise EndpointVerificationError('The WireGuard endpoint could not be verified.')
|
||||
|
||||
66
core/services/keys_and_verifications/wireguard_keys.py
Normal file
66
core/services/keys_and_verifications/wireguard_keys.py
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
from core.models.session.SessionProfile import SessionProfile
|
||||
from core.models.system.SystemProfile import SystemProfile
|
||||
from core.Errors import MissingSubscriptionError, MissingLocationError, InvalidSubscriptionError
|
||||
from core.services.WebServiceApiService import WebServiceApiService
|
||||
from core.controllers.ConnectionController import ConnectionController
|
||||
from core.observers.ConnectionObserver import ConnectionObserver
|
||||
from typing import Union, Optional
|
||||
import base64
|
||||
import re
|
||||
|
||||
def register_wireguard_session(
|
||||
profile: Union[SessionProfile, SystemProfile],
|
||||
connection_observer: Optional[ConnectionObserver] = None
|
||||
):
|
||||
"""Register a WireGuard session for the given profile."""
|
||||
|
||||
if not profile.has_subscription():
|
||||
raise MissingSubscriptionError()
|
||||
|
||||
if not profile.has_location():
|
||||
raise MissingLocationError()
|
||||
|
||||
wireguard_keys = _generate_wireguard_keys()
|
||||
|
||||
wireguard_configuration = ConnectionController.with_preferred_connection(
|
||||
profile.location.country_code,
|
||||
profile.location.code,
|
||||
profile.subscription.billing_code,
|
||||
wireguard_keys.get('public'),
|
||||
task=WebServiceApiService.post_wireguard_session,
|
||||
connection_observer=connection_observer
|
||||
)
|
||||
|
||||
if wireguard_configuration is None:
|
||||
raise InvalidSubscriptionError()
|
||||
|
||||
wireguard_configuration = _inject_private_key(wireguard_configuration, wireguard_keys.get('private'))
|
||||
profile.attach_wireguard_configuration(wireguard_configuration)
|
||||
|
||||
|
||||
def _generate_wireguard_keys() -> dict:
|
||||
"""Generate WireGuard public/private key pair."""
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
|
||||
|
||||
raw_private_key = X25519PrivateKey.generate()
|
||||
|
||||
public_key = raw_private_key.public_key().public_bytes(
|
||||
encoding=serialization.Encoding.Raw, format=serialization.PublicFormat.Raw
|
||||
)
|
||||
|
||||
private_key = raw_private_key.private_bytes(
|
||||
encoding=serialization.Encoding.Raw, format=serialization.PrivateFormat.Raw, encryption_algorithm=serialization.NoEncryption()
|
||||
)
|
||||
|
||||
return dict(
|
||||
private=base64.b64encode(private_key).decode(),
|
||||
public=base64.b64encode(public_key).decode()
|
||||
)
|
||||
|
||||
|
||||
def _inject_private_key(config: str, private_key: str) -> str:
|
||||
"""Inject private key into WireGuard config."""
|
||||
expression = re.compile(r'^(PrivateKey =)\s?$', re.MULTILINE)
|
||||
return re.sub(expression, r'\1 ' + private_key, config)
|
||||
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
from core.services.networking.general_connection_tools.testing_evaluating import system_uses_wireguard_interface
|
||||
from core.services.networking.systemwide.systemwide_wireguard import establish_system_connection, terminate_system_connection
|
||||
from core.services.keys_and_verifications.wireguard_keys import register_wireguard_session
|
||||
from core.models.session.SessionConnection import SessionConnectionTypes
|
||||
from core.models.system.SystemConnection import SystemConnectionTypes
|
||||
|
||||
from core.errors.logger import logger
|
||||
from core.errors.exceptions import *
|
||||
from typing import Union, Optional, Callable
|
||||
from core.models.session.SessionProfile import SessionProfile
|
||||
from core.models.system.SystemProfile import SystemProfile
|
||||
from core.Errors import ConnectionTerminationError, InvalidSubscriptionError
|
||||
from core.services.WebServiceApiService import WebServiceApiService
|
||||
from core.controllers.ConnectionController import ConnectionController
|
||||
from core.models.BaseProfile import ProfileType
|
||||
from core.services.subscriptions.subscriptions import activate_subscription
|
||||
from core.observers.ConnectionObserver import ConnectionObserver
|
||||
from core.controllers.SystemStateController import SystemStateController
|
||||
|
||||
wireguard_types = [SystemConnectionTypes.WIREGUARD, SessionConnectionTypes.WIREGUARD]
|
||||
|
||||
def establish_connection(
|
||||
profile: Union[SessionProfile, SystemProfile],
|
||||
ignore: tuple[type[Exception]] = (),
|
||||
connection_observer: Optional[ConnectionObserver] = None,
|
||||
):
|
||||
"""Establish a connection for the given profile."""
|
||||
|
||||
logger.info(f"[CONNECTION] Checking subscription..")
|
||||
_ensure_subscription_ready(profile, connection_observer)
|
||||
|
||||
logger.info(f"[CONNECTION] Checking proxy configuration..")
|
||||
_ensure_proxy_configured(profile, connection_observer)
|
||||
|
||||
logger.info(f"[CONNECTION] Checking Wireguard configuration..")
|
||||
_ensure_wireguard_configured(profile, connection_observer)
|
||||
|
||||
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)
|
||||
|
||||
|
||||
def _ensure_subscription_ready(
|
||||
profile: Union[SessionProfile, SystemProfile],
|
||||
connection_observer: Optional[ConnectionObserver],
|
||||
):
|
||||
"""Activate subscription (idempotent)."""
|
||||
activate_subscription(profile, connection_observer=connection_observer)
|
||||
|
||||
|
||||
def _ensure_proxy_configured(
|
||||
profile: Union[SessionProfile, SystemProfile],
|
||||
connection_observer: Optional[ConnectionObserver],
|
||||
):
|
||||
"""Setup proxy config if needed."""
|
||||
if not profile.connection.needs_proxy_configuration():
|
||||
return
|
||||
|
||||
_ensure_subscription_ready(profile, connection_observer)
|
||||
|
||||
proxy_config = ConnectionController.with_preferred_connection(
|
||||
profile.subscription.billing_code,
|
||||
task=WebServiceApiService.get_proxy_configuration,
|
||||
connection_observer=connection_observer
|
||||
)
|
||||
|
||||
if proxy_config is None:
|
||||
raise InvalidSubscriptionError()
|
||||
|
||||
profile.attach_proxy_configuration(proxy_config)
|
||||
|
||||
|
||||
def _ensure_wireguard_configured(
|
||||
profile: Union[SessionProfile, SystemProfile],
|
||||
connection_observer: Optional[ConnectionObserver],
|
||||
):
|
||||
"""Setup WireGuard config if needed."""
|
||||
|
||||
logger.info(f"[CONNECTION] Checking if this profile needs a wg config. The profile.connection.code is {profile.connection.code}")
|
||||
if profile.connection.code not in wireguard_types:
|
||||
logger.info(f"[CONNECTION] This profile {profile.id} does NOT need a wireguard config")
|
||||
return
|
||||
|
||||
if profile.type == ProfileType.SYSTEM:
|
||||
if system_uses_wireguard_interface() and SystemStateController.exists():
|
||||
logger.info(f"[CONNECTION] There is a systemwide profile that is already is active.")
|
||||
try:
|
||||
terminate_system_connection()
|
||||
except ConnectionTerminationError as e:
|
||||
logger.error(f"[CONNECTION] Previous Systemwide Wireguard connection could not be disabled. {e}")
|
||||
|
||||
_ensure_subscription_ready(profile, connection_observer)
|
||||
logger.info(f"[CONNECTION] Checking if the profile already has a wg config..")
|
||||
if not profile.has_wireguard_configuration():
|
||||
logger.info(f"[CONNECTION] Profile does NOT have WG keys and it needs it. Registering a NEW wg session..")
|
||||
register_wireguard_session(profile, connection_observer=connection_observer)
|
||||
|
||||
|
||||
def _establish_with_renegotiation(
|
||||
profile: Union[SessionProfile, SystemProfile],
|
||||
establish_fn: Callable,
|
||||
ignore: tuple[type[Exception]],
|
||||
connection_observer: Optional[ConnectionObserver],
|
||||
):
|
||||
"""Attempt connection, renegotiate WireGuard once if needed."""
|
||||
try:
|
||||
logger.info(f"[CONNECTION] Connecting..")
|
||||
return establish_fn(profile, ignore=ignore, connection_observer=connection_observer)
|
||||
except ConnectionError:
|
||||
if __should_renegotiate(profile):
|
||||
logger.info(f"[CONNECTION] Renegotiating a new wg key session with API..")
|
||||
register_wireguard_session(profile, connection_observer=connection_observer)
|
||||
return establish_fn(profile, ignore=ignore, connection_observer=connection_observer)
|
||||
raise ConnectionError('The connection could not be established.')
|
||||
|
||||
|
||||
def __should_renegotiate(profile: Union[SessionProfile, SystemProfile]):
|
||||
|
||||
if not profile.has_subscription():
|
||||
raise MissingSubscriptionError()
|
||||
|
||||
if profile.connection.code in wireguard_types and profile.has_wireguard_configuration():
|
||||
if profile.subscription.has_been_activated():
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
from core.Constants import Constants
|
||||
from core.Errors import CommandNotFoundError
|
||||
from core.observers.ConnectionObserver import ConnectionObserver
|
||||
from essentials.modules.TorModule import TorModule
|
||||
from typing import Optional
|
||||
import os
|
||||
import random
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
|
||||
def terminate_tor_connection():
|
||||
|
||||
tor_module = TorModule(Constants.HV_TOR_STATE_HOME)
|
||||
tor_module.stop_service()
|
||||
|
||||
|
||||
def await_network_interface():
|
||||
|
||||
network_interface_is_activated = False
|
||||
|
||||
retry_interval = .5
|
||||
maximum_number_of_attempts = 10
|
||||
attempt = 0
|
||||
|
||||
while not network_interface_is_activated and attempt < maximum_number_of_attempts:
|
||||
|
||||
time.sleep(retry_interval)
|
||||
|
||||
network_interface_is_activated = system_uses_wireguard_interface()
|
||||
attempt += 1
|
||||
|
||||
if not network_interface_is_activated:
|
||||
raise ConnectionError('The network interface could not be activated.')
|
||||
|
||||
|
||||
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):
|
||||
|
||||
request_urls = [Constants.PING_URL]
|
||||
proxies = None
|
||||
|
||||
if os.environ.get('PING_URL') is None:
|
||||
|
||||
request_urls.extend([
|
||||
'https://hc1.simplifiedprivacy.net',
|
||||
'https://hc2.simplifiedprivacy.org',
|
||||
'https://hc3.hydraveil.net'
|
||||
])
|
||||
|
||||
random.shuffle(request_urls)
|
||||
|
||||
if port_number is not None:
|
||||
proxies = get_proxies(port_number)
|
||||
|
||||
for request_url in request_urls:
|
||||
|
||||
command = [
|
||||
sys.executable, '-u', '-c', 'import requests, sys\n'
|
||||
'try:\n'
|
||||
f' response = requests.get(\'{request_url}\', proxies={proxies}, timeout={timeout})\n'
|
||||
' response.raise_for_status(); print(response.text)\n'
|
||||
'except requests.exceptions.RequestException:\n'
|
||||
' sys.exit(1)'
|
||||
]
|
||||
|
||||
try:
|
||||
|
||||
_response = subprocess.check_output(command, text=True, timeout=timeout)
|
||||
return None
|
||||
|
||||
except (subprocess.CalledProcessError, subprocess.TimeoutExpired):
|
||||
pass
|
||||
|
||||
raise ConnectionError('The connection could not be established.')
|
||||
|
||||
|
||||
# Kept a duplicate in connection controller, because Tor uses it. and circular imports are an issue for now. Refactor later.
|
||||
def get_proxies(port_number: int):
|
||||
|
||||
return dict(
|
||||
http=f'socks5h://127.0.0.1:{port_number}',
|
||||
https=f'socks5h://127.0.0.1:{port_number}'
|
||||
)
|
||||
|
||||
|
||||
def await_connection(port_number: Optional[int] = None, connection_observer: Optional[ConnectionObserver] = None):
|
||||
|
||||
if port_number is None:
|
||||
await_network_interface()
|
||||
|
||||
for retry_count in range(Constants.MAX_CONNECTION_ATTEMPTS):
|
||||
|
||||
if connection_observer is not None:
|
||||
|
||||
connection_observer.notify('connecting', dict(
|
||||
retry_interval=Constants.CONNECTION_RETRY_INTERVAL,
|
||||
maximum_number_of_attempts=Constants.MAX_CONNECTION_ATTEMPTS,
|
||||
attempt_count=retry_count + 1
|
||||
))
|
||||
|
||||
try:
|
||||
|
||||
__test_connection(port_number)
|
||||
return
|
||||
|
||||
except ConnectionError:
|
||||
|
||||
time.sleep(Constants.CONNECTION_RETRY_INTERVAL)
|
||||
retry_count += 1
|
||||
|
||||
raise ConnectionError('The connection could not be established.')
|
||||
123
core/services/networking/systemwide/systemwide_wireguard.py
Normal file
123
core/services/networking/systemwide/systemwide_wireguard.py
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
from core.services.networking.general_connection_tools.testing_evaluating import await_connection, system_uses_wireguard_interface, terminate_tor_connection
|
||||
from core.services.keys_and_verifications.endpoint_verification import verify_wireguard_endpoint
|
||||
|
||||
from core.Constants import Constants
|
||||
from core.Errors import ConnectionTerminationError, CommandNotFoundError
|
||||
from core.controllers.ConfigurationController import ConfigurationController
|
||||
from core.controllers.SystemStateController import SystemStateController
|
||||
from core.models.BaseProfile import ProfileType
|
||||
from core.models.session.SessionProfile import SessionProfile
|
||||
from core.models.system.SystemProfile import SystemProfile
|
||||
from core.models.system.SystemState import SystemState
|
||||
from core.observers.ConnectionObserver import ConnectionObserver
|
||||
from subprocess import CalledProcessError
|
||||
from typing import Optional
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import time
|
||||
|
||||
def terminate_system_connection():
|
||||
|
||||
if shutil.which('nmcli') is None:
|
||||
raise CommandNotFoundError('nmcli')
|
||||
|
||||
if SystemStateController.exists():
|
||||
|
||||
process = subprocess.Popen(('nmcli', 'connection', 'delete', 'wg'), stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
|
||||
completed_successfully = not bool(os.waitpid(process.pid, 0)[1] >> 8)
|
||||
|
||||
if completed_successfully or not system_uses_wireguard_interface():
|
||||
|
||||
subprocess.run(('nmcli', 'connection', 'delete', 'hv-ipv6-sink'), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
terminate_tor_connection()
|
||||
SystemState.dissolve()
|
||||
|
||||
else:
|
||||
raise ConnectionTerminationError('The connection could not be terminated.')
|
||||
|
||||
|
||||
def __establish_system_connection(profile: SystemProfile, connection_observer: Optional[ConnectionObserver] = None):
|
||||
|
||||
if shutil.which('dbus-send') is None:
|
||||
raise CommandNotFoundError('dbus-send')
|
||||
|
||||
if shutil.which('nmcli') is None:
|
||||
raise CommandNotFoundError('nmcli')
|
||||
|
||||
terminate_system_connection()
|
||||
|
||||
try:
|
||||
process_output = subprocess.check_output(('nmcli', 'connection', 'import', '--temporary', 'type', 'wireguard', 'file', profile.get_wireguard_configuration_path()), text=True)
|
||||
except CalledProcessError:
|
||||
raise ConnectionError('The connection could not be established.')
|
||||
|
||||
try:
|
||||
|
||||
connection_id = (m := re.search(r'(?<=\()([a-f0-9-]+?)(?=\))', process_output)) and m.group(1)
|
||||
ipv6_method = subprocess.check_output(('nmcli', '-g', 'ipv6.method', 'connection', 'show', connection_id), text=True).strip()
|
||||
|
||||
except CalledProcessError:
|
||||
raise ConnectionError('The connection could not be established.')
|
||||
|
||||
if ipv6_method in ('disabled', 'ignore'):
|
||||
|
||||
try:
|
||||
subprocess.run(('dbus-send', '--system', '--print-reply', '--dest=org.freedesktop.NetworkManager', '/org/freedesktop/NetworkManager', 'org.freedesktop.DBus.Properties.Set', 'string:org.freedesktop.NetworkManager', 'string:ConnectivityCheckEnabled', 'variant:boolean:false'), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True)
|
||||
except CalledProcessError:
|
||||
raise ConnectionError('The connection could not be established.')
|
||||
|
||||
try:
|
||||
subprocess.run(('nmcli', 'connection', 'add', 'type', 'dummy', 'save', 'no', 'con-name', 'hv-ipv6-sink', 'ifname', 'hvipv6sink0', 'ipv6.method', 'manual', 'ipv6.addresses', 'fd7a:fd4b:54e3:077c::/64', 'ipv6.gateway', 'fd7a:fd4b:54e3:077c::1', 'ipv6.dns', '::1', 'ipv6.route-metric', '72'), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True)
|
||||
except CalledProcessError:
|
||||
raise ConnectionError('The connection could not be established.')
|
||||
|
||||
SystemStateController.create(profile.id)
|
||||
|
||||
try:
|
||||
await_connection(connection_observer=connection_observer)
|
||||
|
||||
except ConnectionError:
|
||||
raise ConnectionError('The connection could not be established.')
|
||||
|
||||
|
||||
|
||||
def establish_system_connection(profile: SystemProfile, ignore: tuple[type[Exception]] = (), connection_observer: Optional[ConnectionObserver] = None):
|
||||
|
||||
if ConfigurationController.get_endpoint_verification_enabled():
|
||||
verify_wireguard_endpoint(profile, ignore=ignore)
|
||||
|
||||
try:
|
||||
__establish_system_connection(profile, connection_observer)
|
||||
|
||||
except ConnectionError:
|
||||
|
||||
try:
|
||||
terminate_system_connection()
|
||||
except ConnectionTerminationError:
|
||||
pass
|
||||
|
||||
raise ConnectionError('The connection could not be established.')
|
||||
|
||||
except CalledProcessError:
|
||||
|
||||
try:
|
||||
terminate_system_connection()
|
||||
except ConnectionTerminationError:
|
||||
pass
|
||||
|
||||
try:
|
||||
__establish_system_connection(profile, connection_observer)
|
||||
|
||||
except (ConnectionError, CalledProcessError):
|
||||
|
||||
try:
|
||||
terminate_system_connection()
|
||||
except ConnectionTerminationError:
|
||||
pass
|
||||
|
||||
raise ConnectionError('The connection could not be established.')
|
||||
|
||||
terminate_tor_connection()
|
||||
time.sleep(1.0)
|
||||
61
core/services/subscriptions/subscriptions.py
Normal file
61
core/services/subscriptions/subscriptions.py
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
from typing import Union, Optional
|
||||
from core.models.session.SessionProfile import SessionProfile
|
||||
from core.models.system.SystemProfile import SystemProfile
|
||||
from core.Errors import MissingSubscriptionError, InvalidSubscriptionError
|
||||
from core.services.WebServiceApiService import WebServiceApiService
|
||||
from core.controllers.ConnectionController import ConnectionController
|
||||
from core.observers.ConnectionObserver import ConnectionObserver
|
||||
|
||||
def activate_subscription(
|
||||
profile: Union[SessionProfile, SystemProfile],
|
||||
connection_observer: Optional[ConnectionObserver] = None
|
||||
) -> bool:
|
||||
"""
|
||||
Purpose:
|
||||
Ensure subscription is ready.
|
||||
|
||||
Features:
|
||||
Idempotent.
|
||||
Checks local first
|
||||
|
||||
Confusion:
|
||||
This returns True both if was already active or just activated.
|
||||
|
||||
Returns:
|
||||
Returns True if subscription was already active.
|
||||
Returns True if subscription was just activated.
|
||||
|
||||
Errors:
|
||||
True.
|
||||
Raises if subscription is missing or invalid. (not false)
|
||||
"""
|
||||
|
||||
if not profile.has_subscription():
|
||||
raise MissingSubscriptionError()
|
||||
|
||||
# Already activated—nothing to do
|
||||
if profile.subscription.has_been_activated():
|
||||
return True
|
||||
|
||||
# Fetch and activate
|
||||
subscription = ConnectionController.with_preferred_connection(
|
||||
profile.subscription.billing_code,
|
||||
task=WebServiceApiService.get_subscription,
|
||||
connection_observer=connection_observer
|
||||
)
|
||||
|
||||
if subscription is None:
|
||||
raise InvalidSubscriptionError()
|
||||
|
||||
profile.subscription = subscription
|
||||
profile.save()
|
||||
return True
|
||||
|
||||
|
||||
def is_subscription_ready(profile: Union[SessionProfile, SystemProfile]) -> bool:
|
||||
"""Check if subscription exists and is activated (no side effects)."""
|
||||
return (
|
||||
profile.has_subscription()
|
||||
and profile.subscription.has_been_activated()
|
||||
)
|
||||
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
[project]
|
||||
name = "sp-hydra-veil-core"
|
||||
version = "2.4.0"
|
||||
version = "2.4.2"
|
||||
authors = [
|
||||
{ name = "Simplified Privacy" },
|
||||
]
|
||||
|
|
|
|||
Loading…
Reference in a new issue