diff --git a/Furious/Core/CoreManager.py b/Furious/Core/CoreManager.py index 896259a9..96be2c37 100644 --- a/Furious/Core/CoreManager.py +++ b/Furious/Core/CoreManager.py @@ -29,10 +29,11 @@ from Furious.Core.Hysteria1 import * from Furious.Core.Hysteria2 import * from Furious.Core.Tun2socks import * -from typing import Tuple, Union +from typing import Callable, Tuple, Union import uuid import logging +import tempfile import functools import subprocess @@ -286,6 +287,22 @@ class CoreManager(Mixins.CleanupOnExit): return process, success + @staticmethod + def waitForTUNDeviceBroughtUp(func: Callable[[str], bool], deviceName: str) -> bool: + for counter in range(0, 10000, 100): + if func(deviceName): + logger.info( + f'find TUN device \'{deviceName}\' success. Counter: {counter}' + ) + + return True + + PySide6Legacy.eventLoopWait(100) + + logger.error(f'find TUN device \'{deviceName}\' failed') + + return False + def start( self, config: ConfigFactory, @@ -324,297 +341,348 @@ class CoreManager(Mixins.CleanupOnExit): # TUN Mode handling if not proxyModeOnly and SystemRuntime.isTUNMode(): - # Currently TUN Mode is only supported on Windows and macOS - if PLATFORM == 'Windows' or PLATFORM == 'Darwin': - if PLATFORM == 'Windows': - # cleanup first - SystemRoutingTable.delete( - '0.0.0.0', APPLICATION_TUN_GATEWAY_ADDRESS - ) + if PLATFORM == 'Windows': + # cleanup first + SystemRoutingTable.delete('0.0.0.0', APPLICATION_TUN_GATEWAY_ADDRESS) - # Handle user defined settings - userGateway, userInterfaceIP = ( - userDefaultPrimaryGatewayIP(), - userPrimaryAdapterInterfaceIP(), + # Handle user defined settings + userGateway, userInterfaceIP = ( + userDefaultPrimaryGatewayIP(), + userPrimaryAdapterInterfaceIP(), + ) + + if userGateway and userInterfaceIP: + logger.info( + f'got user defined TUN settings. ' + f'\'DefaultPrimaryGatewayIP\': {userGateway}. ' + f'\'PrimaryAdapterInterfaceIP\': {userInterfaceIP}' ) - if userGateway and userInterfaceIP: - logger.info( - f'got user defined TUN settings. ' - f'\'DefaultPrimaryGatewayIP\': {userGateway}. ' - f'\'PrimaryAdapterInterfaceIP\': {userInterfaceIP}' - ) + gateway, interface = userGateway, userInterfaceIP + else: + logger.info( + f'automatically fetching TUN settings: ' + f'\'DefaultPrimaryGatewayIP\' and \'PrimaryAdapterInterfaceIP\'' + ) - gateway, interfaceIP = userGateway, userInterfaceIP - else: - logger.info( - f'automatically fetching TUN settings: ' - f'\'DefaultPrimaryGatewayIP\' and \'PrimaryAdapterInterfaceIP\'' - ) + defaultGateway = SystemRoutingTable.getDefaultGateway() - defaultGateway = SystemRoutingTable.getDefaultGateway() - - if PLATFORM == 'Darwin': - # Need this? - defaultGateway = list( - # Filter TUN Gateway - filter( - lambda x: x != APPLICATION_TUN_GATEWAY_ADDRESS, - defaultGateway, - ) + if PLATFORM == 'Darwin': + # Need this? + defaultGateway = list( + # Filter TUN Gateway + filter( + lambda x: x != APPLICATION_TUN_GATEWAY_ADDRESS, + defaultGateway, ) - - if len(defaultGateway) != 1: - logger.error(f'bad default gateway: {defaultGateway}') - - return False - - if PLATFORM == 'Windows': - gateway, interfaceIP = defaultGateway[0] - else: - gateway, interfaceIP = defaultGateway[0], None - - tun = Tun2socks(exitCallback=exitCallback, msgCallback=msgCallbackTUN_) - self.processesPool.append(tun) - - tcpSendBufferSize, tcpReceiveBufferSize, tcpAutoTuning = ( - userTcpSendBufferSize(), - userTcpReceiveBufferSize(), - userTcpAutoTuning(), - ) - - if tcpSendBufferSize != 1: - logger.info( - f'got user defined TUN settings. TcpSendBufferSize: {tcpSendBufferSize}' ) - if tcpReceiveBufferSize != 1: - logger.info( - f'got user defined TUN settings. TCPReceiveBufferSize: {tcpReceiveBufferSize}' - ) + if len(defaultGateway) != 1: + logger.error(f'bad default gateway: {defaultGateway}') - if tcpAutoTuning == 'False': - tcpAutoTuning = False - elif tcpAutoTuning == 'True': - tcpAutoTuning = True - - logger.info( - f'got user defined TUN settings. TcpAutoTuning: {tcpAutoTuning}' - ) - else: - tcpAutoTuning = False - - if not tun.start( - APPLICATION_TUN_DEVICE_NAME, - APPLICATION_TUN_NETWORK_INTERFACE_NAME, - 'error', - f'socks5://{configcopy.socksProxy()}', - '', - f'{tcpSendBufferSize}MB', - f'{tcpReceiveBufferSize}MB', - tcpAutoTuning, - ): return False - # Handle user defined settings - bypassTUN = userBypassTUNAdapterInterfaceIP() + if PLATFORM == 'Windows' or PLATFORM == 'Linux': + # On Linux the 'interface' is a name + gateway, interface = defaultGateway[0] + elif PLATFORM == 'Darwin': + gateway, interface = defaultGateway[0], None + else: + logger.error(f'unrecognized platform: {PLATFORM}') - if bypassTUN: - try: - bypassSplit = bypassTUN.split(',') - except Exception as ex: - # Any non-exit exceptions + return False - logger.error( - f'error when processing user TUN bypass settings: {ex}' - ) + tun = Tun2socks(exitCallback=exitCallback, msgCallback=msgCallbackTUN_) + self.processesPool.append(tun) + + tcpSendBufferSize, tcpReceiveBufferSize, tcpAutoTuning = ( + userTcpSendBufferSize(), + userTcpReceiveBufferSize(), + userTcpAutoTuning(), + ) + + if tcpSendBufferSize != 1: + logger.info( + f'got user defined TUN settings. TcpSendBufferSize: {tcpSendBufferSize}' + ) + + if tcpReceiveBufferSize != 1: + logger.info( + f'got user defined TUN settings. TCPReceiveBufferSize: {tcpReceiveBufferSize}' + ) + + if tcpAutoTuning == 'False': + tcpAutoTuning = False + elif tcpAutoTuning == 'True': + tcpAutoTuning = True + + logger.info( + f'got user defined TUN settings. TcpAutoTuning: {tcpAutoTuning}' + ) + else: + tcpAutoTuning = False + + if PLATFORM != 'Linux': + interfaceArg = APPLICATION_TUN_NETWORK_INTERFACE_NAME + else: + interfaceArg = interface + + startTUN = functools.partial( + tun.start, + APPLICATION_TUN_DEVICE_NAME, + interfaceArg, + 'error', + f'socks5://{configcopy.socksProxy()}', + '', + f'{tcpSendBufferSize}MB', + f'{tcpReceiveBufferSize}MB', + tcpAutoTuning, + ) + + if PLATFORM != 'Linux': + # Windows & macOS: bring up TUN first + if not startTUN(): + return False + + # Handle user defined settings + bypassTUN = userBypassTUNAdapterInterfaceIP() + + if bypassTUN: + try: + bypassSplit = bypassTUN.split(',') + except Exception as ex: + # Any non-exit exceptions + + logger.error( + f'error when processing user TUN bypass settings: {ex}' + ) + + SystemRoutingTable.Relations.clear() + + return False + else: + for bypass in bypassSplit: + if isValidIPAddress(bypass): + logger.info(f'processing user TUN bypass IP: {bypass}') + + SystemRoutingTable.Relations.append([bypass, gateway]) + else: + logger.error( + f'invalid IP address when processing ' + f'user TUN bypass settings: {bypass}' + ) + + SystemRoutingTable.Relations.clear() + + return False + else: + logger.info( + f'automatically fetching TUN settings: ' + f'\'BypassTUNAdapterInterfaceIP\'' + ) + + address = configcopy.itemAddress + + if not isValidIPAddress(address): + DNSResolver.configureHttpProxy(configcopy.httpProxy()) + + error, resolved = DNSResolver.resolve(address) + + if error: + logger.error(f'DNS resolution failed: {address}') SystemRoutingTable.Relations.clear() return False else: - for bypass in bypassSplit: - if isValidIPAddress(bypass): - logger.info(f'processing user TUN bypass IP: {bypass}') + for address in resolved: + SystemRoutingTable.Relations.append([address, gateway]) + else: + SystemRoutingTable.Relations.append([address, gateway]) - SystemRoutingTable.Relations.append([bypass, gateway]) - else: - logger.error( - f'invalid IP address when processing ' - f'user TUN bypass settings: {bypass}' - ) + # Platform specific implementation + if PLATFORM == 'Windows': + if not self.waitForTUNDeviceBroughtUp( + SystemRoutingTable.WIN32IpconfigFindContent, + APPLICATION_TUN_DEVICE_NAME, + ): + return False - SystemRoutingTable.Relations.clear() + # Handle user defined settings + userInterfaceName = userPrimaryAdapterInterfaceName() - return False + if userInterfaceName: + logger.info( + f'got user defined TUN settings. ' + f'\'PrimaryAdapterInterfaceName\': {userInterfaceName}' + ) + + alias = userInterfaceName else: logger.info( f'automatically fetching TUN settings: ' - f'\'BypassTUNAdapterInterfaceIP\'' + f'\'PrimaryAdapterInterfaceName\'' ) - address = configcopy.itemAddress + alias = SystemRoutingTable.WIN32GetInterfaceAliasByIP(interface) - if not isValidIPAddress(address): - DNSResolver.configureHttpProxy(configcopy.httpProxy()) + if alias: - error, resolved = DNSResolver.resolve(address) + def _windowsCleanup(_alias): + SystemRoutingTable.WIN32SetInterfaceDNS(_alias) + SystemRoutingTable.WIN32FlushDNSCache() - if error: - logger.error(f'DNS resolution failed: {address}') + tun.cleanup = functools.partial(_windowsCleanup, alias) - SystemRoutingTable.Relations.clear() + # Handle user defined settings + userDisableInterfaceDNS = userDisablePrimaryAdapterInterfaceDNS() - return False - else: - for address in resolved: - SystemRoutingTable.Relations.append([address, gateway]) - else: - SystemRoutingTable.Relations.append([address, gateway]) + logger.info( + f'DisablePrimaryInterfaceDNS: {userDisableInterfaceDNS}' + ) - if PLATFORM == 'Windows': - foundDevice = False - - for counter in range(0, 10000, 100): - try: - result = runExternalCommand( - 'ipconfig', - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - check=True, - ) - except Exception: - # Any non-exit exceptions - - break - else: - stdout = result.stdout.decode('utf-8', 'replace') - - if stdout.find(APPLICATION_TUN_DEVICE_NAME) >= 0: - foundDevice = True - - logger.info( - f'find TUN device \'{APPLICATION_TUN_DEVICE_NAME}\' success. ' - f'Counter: {counter}' - ) - - break - - PySide6Legacy.eventLoopWait(100) - - if not foundDevice: - logger.error( - f'find TUN device \'{APPLICATION_TUN_DEVICE_NAME}\' failed' + if userDisableInterfaceDNS != 'False': + SystemRoutingTable.WIN32SetInterfaceDNS( + alias, '127.0.0.1', False ) + # Handle user defined settings + userTunInterfaceDNS = userTunAdapterInterfaceDNS() + + if userTunInterfaceDNS == '': + userTunInterfaceDNS = APPLICATION_TUN_INTERFACE_DNS_ADDRESS + else: + logger.info( + f'got user defined TUN settings. ' + f'TunAdapterInterfaceDNS: {userTunInterfaceDNS}' + ) + + SystemRoutingTable.addRelations() + SystemRoutingTable.WIN32SetInterfaceDNS( + APPLICATION_TUN_DEVICE_NAME, + userTunInterfaceDNS, + False, + ) + SystemRoutingTable.setDeviceGateway( + APPLICATION_TUN_DEVICE_NAME, + APPLICATION_TUN_IP_ADDRESS, + APPLICATION_TUN_GATEWAY_ADDRESS, + ) + SystemRoutingTable.WIN32FlushDNSCache() + + # Platform specific implementation + if PLATFORM == 'Darwin': + for address in [ + *list(f'{2 ** (8 - x)}.0.0.0/{x}' for x in range(8, 0, -1)), + '198.18.0.0/15', + ]: + SystemRoutingTable.Relations.append( + [address, APPLICATION_TUN_GATEWAY_ADDRESS] + ) + + servers = SystemRoutingTable.DarwinGetDNSServers() + + def _darwinCleanup(_servers): + for _service, _dnsserver in _servers: + SystemRoutingTable.DarwinSetDNSServers(_service, _dnsserver) + + tun.cleanup = functools.partial(_darwinCleanup, servers) + + # Handle user defined settings + userTunInterfaceDNS = userTunAdapterInterfaceDNS() + + if userTunInterfaceDNS == '': + userTunInterfaceDNS = APPLICATION_TUN_INTERFACE_DNS_ADDRESS + else: + logger.info( + f'got user defined TUN settings. ' + f'TunAdapterInterfaceDNS: {userTunInterfaceDNS}' + ) + + for service, dnsserver in servers: + SystemRoutingTable.DarwinSetDNSServers( + service, + userTunInterfaceDNS, + ) + + SystemRoutingTable.setDeviceGateway( + APPLICATION_TUN_DEVICE_NAME, + APPLICATION_TUN_IP_ADDRESS, + APPLICATION_TUN_GATEWAY_ADDRESS, + ) + SystemRoutingTable.addRelations() + + # Platform specific implementation + if PLATFORM == 'Linux': + + def _linuxCleanup(): + SystemRoutingTable.LinuxDeleteTUNDevice(APPLICATION_TUN_DEVICE_NAME) + + tun.cleanup = functools.partial(_linuxCleanup) + + if SystemRoutingTable.LinuxFindTUNDevice(APPLICATION_TUN_DEVICE_NAME): + logger.info( + f'find TUN device {APPLICATION_TUN_DEVICE_NAME} success. ' + f'Will not try to bring up TUN device again' + ) + + commandBringUpTUN = '' + else: + logger.info( + f'find TUN device {APPLICATION_TUN_DEVICE_NAME} failed. ' + f'Will try to bring up TUN device' + ) + + commandBringUpTUN = ( + f'ip tuntap add mode tun dev {APPLICATION_TUN_DEVICE_NAME}\n' + f'ip addr add 10.10.10.10/24 dev {APPLICATION_TUN_DEVICE_NAME}\n' + f'ip link set dev {APPLICATION_TUN_DEVICE_NAME} up' + ) + + commandAddDefaultRoute = ( + f'ip route add default dev {APPLICATION_TUN_DEVICE_NAME} metric 5' + ) + + def route(source, destination) -> str: + return f'{source} via {destination} dev {interface}' + + iproute = SystemRoutingTable.LinuxGetIpRoute() + + commandBypass = '\n'.join( + list( + f'ip route add {route(sourceIP, destinationIP)}' + for sourceIP, destinationIP in SystemRoutingTable.Relations + if iproute.find(route(sourceIP, destinationIP)) == -1 + ) + ) + + with tempfile.NamedTemporaryFile( + mode='w', encoding='utf-8', suffix='.sh', delete=True + ) as file: + content = '\n'.join( + filter( + lambda x: x != '', + [commandBringUpTUN, commandAddDefaultRoute, commandBypass], + ) + ) + + file.write(content) + file.flush() + + if not SystemRoutingTable.LinuxExecutePrivilegedScript( + file.name, shell='bash' + ): return False - # Handle user defined settings - userInterfaceName = userPrimaryAdapterInterfaceName() - - if userInterfaceName: - logger.info( - f'got user defined TUN settings. ' - f'\'PrimaryAdapterInterfaceName\': {userInterfaceName}' - ) - - alias = userInterfaceName - else: - logger.info( - f'automatically fetching TUN settings: ' - f'\'PrimaryAdapterInterfaceName\'' - ) - - alias = SystemRoutingTable.WIN32GetInterfaceAliasByIP( - interfaceIP - ) - - if alias: - - def _windowsCleanup(_alias): - SystemRoutingTable.WIN32SetInterfaceDNS(_alias) - SystemRoutingTable.WIN32FlushDNSCache() - - tun.cleanup = functools.partial(_windowsCleanup, alias) - - # Handle user defined settings - userDisableInterfaceDNS = ( - userDisablePrimaryAdapterInterfaceDNS() - ) - - logger.info( - f'DisablePrimaryInterfaceDNS: {userDisableInterfaceDNS}' - ) - - if userDisableInterfaceDNS != 'False': - SystemRoutingTable.WIN32SetInterfaceDNS( - alias, '127.0.0.1', False - ) - - # Handle user defined settings - userTunInterfaceDNS = userTunAdapterInterfaceDNS() - - if userTunInterfaceDNS == '': - userTunInterfaceDNS = APPLICATION_TUN_INTERFACE_DNS_ADDRESS - else: - logger.info( - f'got user defined TUN settings. ' - f'TunAdapterInterfaceDNS: {userTunInterfaceDNS}' - ) - - SystemRoutingTable.addRelations() - SystemRoutingTable.WIN32SetInterfaceDNS( + if not self.waitForTUNDeviceBroughtUp( + SystemRoutingTable.LinuxFindTUNDevice, APPLICATION_TUN_DEVICE_NAME, - userTunInterfaceDNS, - False, - ) - SystemRoutingTable.setDeviceGateway( - APPLICATION_TUN_DEVICE_NAME, - APPLICATION_TUN_IP_ADDRESS, - APPLICATION_TUN_GATEWAY_ADDRESS, - ) - SystemRoutingTable.WIN32FlushDNSCache() + ): + return False - if PLATFORM == 'Darwin': - for source in [ - *list(f'{2 ** (8 - x)}.0.0.0/{x}' for x in range(8, 0, -1)), - '198.18.0.0/15', - ]: - SystemRoutingTable.Relations.append( - [source, APPLICATION_TUN_GATEWAY_ADDRESS] - ) - - servers = SystemRoutingTable.DarwinGetDNSServers() - - def _darwinCleanup(_servers): - for _service, _dnsserver in _servers: - SystemRoutingTable.DarwinSetDNSServers(_service, _dnsserver) - - tun.cleanup = functools.partial(_darwinCleanup, servers) - - # Handle user defined settings - userTunInterfaceDNS = userTunAdapterInterfaceDNS() - - if userTunInterfaceDNS == '': - userTunInterfaceDNS = APPLICATION_TUN_INTERFACE_DNS_ADDRESS - else: - logger.info( - f'got user defined TUN settings. ' - f'TunAdapterInterfaceDNS: {userTunInterfaceDNS}' - ) - - for service, dnsserver in servers: - SystemRoutingTable.DarwinSetDNSServers( - service, - userTunInterfaceDNS, - ) - - SystemRoutingTable.setDeviceGateway( - APPLICATION_TUN_DEVICE_NAME, - APPLICATION_TUN_IP_ADDRESS, - APPLICATION_TUN_GATEWAY_ADDRESS, - ) - SystemRoutingTable.addRelations() + # Now bring up TUN + if not startTUN(): + return False return True diff --git a/Furious/Core/Tun2socks.py b/Furious/Core/Tun2socks.py index 0415082a..11d4be9f 100644 --- a/Furious/Core/Tun2socks.py +++ b/Furious/Core/Tun2socks.py @@ -104,9 +104,18 @@ class Tun2socks(CoreProcessWorker): ) def stop(self): - SystemRoutingTable.deleteRelations() + if PLATFORM == 'Linux': + # Stop tunnel first + super().stop() - if callable(self.cleanup): - self.cleanup() + SystemRoutingTable.deleteRelations() - super().stop() + if callable(self.cleanup): + self.cleanup() + else: + SystemRoutingTable.deleteRelations() + + if callable(self.cleanup): + self.cleanup() + + super().stop() diff --git a/Furious/Frozenlib/Constants.py b/Furious/Frozenlib/Constants.py index 5012549f..00e5f450 100644 --- a/Furious/Frozenlib/Constants.py +++ b/Furious/Frozenlib/Constants.py @@ -88,6 +88,8 @@ if PLATFORM == 'Windows': APPLICATION_TUN_DEVICE_NAME = APPLICATION_NAME elif PLATFORM == 'Darwin': APPLICATION_TUN_DEVICE_NAME = 'utun777' +elif PLATFORM == 'Linux': + APPLICATION_TUN_DEVICE_NAME = 'utun777' else: APPLICATION_TUN_DEVICE_NAME = '' diff --git a/Furious/Frozenlib/SystemRoutingTable.py b/Furious/Frozenlib/SystemRoutingTable.py index e6870dc1..b99d0550 100644 --- a/Furious/Frozenlib/SystemRoutingTable.py +++ b/Furious/Frozenlib/SystemRoutingTable.py @@ -54,6 +54,9 @@ class SystemRoutingTable: DEFAULT_GATEWAY_MACOS = re.compile( r'gateway:\s*(\S+)', ) + DEFAULT_GATEWAY_LINUX = re.compile( + r'default\s+via\s+(\S+)\s+dev\s+(\S+)', + ) @staticmethod def add(sourceIP, destinationIP): @@ -208,6 +211,35 @@ class SystemRoutingTable: f'{dictRepr(result.returncode, result.stdout, result.stderr)}' ) + @staticmethod + def WIN32IpconfigFindContent(content: str) -> bool: + assert PLATFORM == 'Windows' + + try: + result = runExternalCommand( + ['ipconfig'], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + check=True, + ) + except subprocess.CalledProcessError as err: + logger.error( + f'run ipconfig failed. ' + f'{dictRepr(err.returncode, err.stdout, err.stderr)}' + ) + + return False + except Exception as ex: + # Any non-exit exceptions + + logger.error(f'run ipconfig failed. {ex}') + + return False + else: + stdout = result.stdout.decode('utf-8', 'replace').strip() + + return stdout.find(content) >= 0 + @staticmethod def DarwinGetDNSServers() -> list: def getNetworkServices(): @@ -239,7 +271,7 @@ class SystemRoutingTable: check=True, ) - dnsserver = result.stdout.decode().strip() + dnsserver = result.stdout.decode('utf-8', 'replace').strip() if dnsserver.find('DNS Servers') >= 0: # 'There aren't any DNS Servers set on ...' @@ -299,6 +331,128 @@ class SystemRoutingTable: f'{dictRepr(result.returncode, result.stdout, result.stderr)}' ) + @staticmethod + def LinuxFindTUNDevice(deviceName: str) -> bool: + assert PLATFORM == 'Linux' + + try: + result = runExternalCommand( + 'ip tuntap show'.split(), + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + check=True, + ) + except subprocess.CalledProcessError as err: + logger.error( + f'find TUN device {deviceName} failed. ' + f'{dictRepr(err.returncode, err.stdout, err.stderr)}' + ) + + return False + except Exception as ex: + # Any non-exit exceptions + + logger.error(f'find TUN device {deviceName} failed. {ex}') + + return False + else: + stdout = result.stdout.decode('utf-8', 'replace').strip() + + return stdout.find(deviceName) >= 0 + + @staticmethod + def LinuxDeleteTUNDevice(deviceName: str) -> bool: + assert PLATFORM == 'Linux' + + try: + result = runExternalCommand( + 'ip tuntap del mode tun dev'.split() + [deviceName], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + check=True, + ) + except subprocess.CalledProcessError as err: + logger.error( + f'delete TUN device {deviceName} failed. ' + f'{dictRepr(err.returncode, err.stdout, err.stderr)}' + ) + + return False + except Exception as ex: + # Any non-exit exceptions + + logger.error(f'delete TUN device {deviceName} failed. {ex}') + + return False + else: + logger.info( + f'delete TUN device {deviceName} success. ' + f'{dictRepr(result.returncode, result.stdout, result.stderr)}' + ) + + return True + + @staticmethod + def LinuxExecutePrivilegedScript(filepath, shell='bash') -> bool: + assert PLATFORM == 'Linux' + + try: + result = runExternalCommand( + ['pkexec', shell, filepath], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + check=True, + ) + except subprocess.CalledProcessError as err: + logger.error( + f'execute privileged script failed. ' + f'{dictRepr(err.returncode, err.stdout, err.stderr)}' + ) + + return False + except Exception as ex: + # Any non-exit exceptions + + logger.error(f'execute privileged script failed. {ex}') + + return False + else: + logger.info( + f'execute privileged script success. ' + f'{dictRepr(result.returncode, result.stdout, result.stderr)}' + ) + + return True + + @staticmethod + def LinuxGetIpRoute() -> str: + assert PLATFORM == 'Linux' + + try: + result = runExternalCommand( + 'ip route show'.split(), + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + check=True, + ) + except subprocess.CalledProcessError as err: + logger.error( + f'show IP route failed. ' + f'{dictRepr(err.returncode, err.stdout, err.stderr)}' + ) + + return '' + except Exception as ex: + # Any non-exit exceptions + + logger.error(f'show IP route failed. {ex}') + + return '' + else: + stdout = result.stdout.decode('utf-8', 'replace').strip() + + return stdout + @staticmethod def getDefaultGateway() -> list: def _get(): @@ -328,6 +482,18 @@ class SystemRoutingTable: result.stdout.decode(SYSTEM_PREFERRED_ENCODING, 'replace') ) + if PLATFORM == 'Linux': + result = runExternalCommand( + 'ip route show default'.split(), + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + check=True, + ) + + return SystemRoutingTable.DEFAULT_GATEWAY_LINUX.findall( + result.stdout.decode(SYSTEM_PREFERRED_ENCODING, 'replace') + ) + try: defaultGateway = _get() except subprocess.CalledProcessError as err: @@ -438,6 +604,10 @@ class SystemRoutingTable: else: return result.returncode, result.stdout, result.stderr + if PLATFORM == 'Linux': + # TODO: Do nothing under Linux + return + try: returncode, stdout, stderr = _delete() except Exception as ex: diff --git a/Furious/TrayActions/Connect.py b/Furious/TrayActions/Connect.py index e8e76ef1..128f2dc6 100644 --- a/Furious/TrayActions/Connect.py +++ b/Furious/TrayActions/Connect.py @@ -107,11 +107,10 @@ class ConnectAction(AppQAction): APP().systemTray.RoutingAction.setDisabled(value) APP().systemTray.SystemProxyAction.setDisabled(value) - if SystemRuntime.isAdmin(): + # TODO: Need this? + if PLATFORM == 'Linux' or SystemRuntime.isAdmin(): TUNModeAction = APP().systemTray.SettingsAction.getTUNModeAction() - - if TUNModeAction is not None: - TUNModeAction.setDisabled(value) + TUNModeAction.setDisabled(value) def isConnected(self) -> bool: return self.textCompare('Disconnect') diff --git a/Furious/TrayActions/Settings.py b/Furious/TrayActions/Settings.py index 690c77bd..6eab7d5a 100644 --- a/Furious/TrayActions/Settings.py +++ b/Furious/TrayActions/Settings.py @@ -52,15 +52,21 @@ _TRANSLATABLE_TUN_MODE = [ class TUNModeAction(AppQAction): def __init__(self, **kwargs): - if SystemRuntime.isAdmin(): + if PLATFORM == 'Linux': super().__init__(_('TUN Mode'), **kwargs) else: - super().__init__(_(f'TUN Mode Disabled ({ADMINISTRATOR_NAME})'), **kwargs) + if SystemRuntime.isAdmin(): + super().__init__(_('TUN Mode'), **kwargs) + else: + super().__init__( + _(f'TUN Mode Disabled ({ADMINISTRATOR_NAME})'), **kwargs + ) - self.setDisabled(True) + self.setDisabled(True) def triggeredCallback(self, checked): - assert SystemRuntime.isAdmin() + if PLATFORM != 'Linux': + assert SystemRuntime.isAdmin() if checked: AppSettings.turnON_('VPNMode') @@ -156,22 +162,19 @@ class SettingsChildAction(AppQAction): class SettingsAction(AppQAction): def __init__(self, **kwargs): - if PLATFORM == 'Windows' or PLATFORM == 'Darwin': - tunActions = [ - TUNModeAction( - checkable=True, - checked=AppSettings.isStateON_('VPNMode'), - ), - AppQAction( - _('Customize TUN Settings...'), - icon=bootstrapIcon('diagram-3.svg'), - checkable=False, - callback=lambda: APP().mainWindow.getGuiTUNSettings().open(), - ), - AppQSeperator(), - ] - else: - tunActions = [] + tunActions = [ + TUNModeAction( + checkable=True, + checked=AppSettings.isStateON_('VPNMode'), + ), + AppQAction( + _('Customize TUN Settings...'), + icon=bootstrapIcon('diagram-3.svg'), + checkable=False, + callback=lambda: APP().mainWindow.getGuiTUNSettings().open(), + ), + AppQSeperator(), + ] if PLATFORM == 'Darwin': hideDockIconAction = [ @@ -239,9 +242,6 @@ class SettingsAction(AppQAction): **kwargs, ) - def getTUNModeAction(self) -> Union[AppQAction, None]: - if PLATFORM == 'Windows' or PLATFORM == 'Darwin': - # 1st action - return self._menu.actions()[0] - else: - return None + def getTUNModeAction(self) -> AppQAction: + # 1st action + return self._menu.actions()[0] diff --git a/Furious/Utility/SystemRuntime.py b/Furious/Utility/SystemRuntime.py index 3fa97552..4e391aad 100644 --- a/Furious/Utility/SystemRuntime.py +++ b/Furious/Utility/SystemRuntime.py @@ -46,7 +46,8 @@ class SystemRuntime: values = dict( list(line.split('=')) for line in filter( - lambda x: x != '', result.stdout.decode().split('\n') + lambda x: x != '', + result.stdout.decode('utf-8', 'replace').split('\n'), ) ) @@ -79,7 +80,10 @@ class SystemRuntime: @staticmethod def isTUNMode() -> bool: - return SystemRuntime.isAdmin() and AppSettings.isStateON_('VPNMode') + if PLATFORM == 'Linux': + return AppSettings.isStateON_('VPNMode') + else: + return AppSettings.isStateON_('VPNMode') and SystemRuntime.isAdmin() @staticmethod @functools.lru_cache(None) diff --git a/Furious/Widget/GuiTUNSettings.py b/Furious/Widget/GuiTUNSettings.py index 5357e86c..3fdaab07 100644 --- a/Furious/Widget/GuiTUNSettings.py +++ b/Furious/Widget/GuiTUNSettings.py @@ -233,7 +233,7 @@ class GuiTUNSettings(GuiEditorWidgetQDialog): self.setTabText(_('Customize TUN Settings')) - if PLATFORM == 'Darwin': + if PLATFORM == 'Darwin' or PLATFORM == 'Linux': self.setFixedSize(int(550 * GOLDEN_RATIO), int(550)) else: self.setFixedSize(int(480 * GOLDEN_RATIO), int(480)) diff --git a/Furious/Window/AppMainWindow.py b/Furious/Window/AppMainWindow.py index 4b4c0867..e00659dd 100644 --- a/Furious/Window/AppMainWindow.py +++ b/Furious/Window/AppMainWindow.py @@ -238,18 +238,15 @@ class AppMainWindow(AppQMainWindow): ), ] - if PLATFORM == 'Windows' or PLATFORM == 'Darwin': - customizeTUNSettingsAction = [ - AppQAction( - _('Customize TUN Settings...'), - icon=bootstrapIcon('diagram-3.svg'), - checkable=False, - callback=lambda: self.getGuiTUNSettings().open(), - ), - AppQSeperator(), - ] - else: - customizeTUNSettingsAction = [] + customizeTUNSettingsAction = [ + AppQAction( + _('Customize TUN Settings...'), + icon=bootstrapIcon('diagram-3.svg'), + checkable=False, + callback=lambda: self.getGuiTUNSettings().open(), + ), + AppQSeperator(), + ] if PLATFORM == 'Windows' or PLATFORM == 'Darwin': _TRANSLATABLE_RESTART_AS_ADMIN = [