Add Linux TUN support

Signed-off-by: Loren Eteval <loren.eteval@proton.me>
This commit is contained in:
Loren Eteval
2025-12-03 20:17:58 +08:00
parent 270432c5ae
commit b74c487cf7
9 changed files with 553 additions and 304 deletions
+322 -254
View File
@@ -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
+13 -4
View File
@@ -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()
+2
View File
@@ -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 = ''
+171 -1
View File
@@ -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:
+3 -4
View File
@@ -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')
+26 -26
View File
@@ -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]
+6 -2
View File
@@ -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)
+1 -1
View File
@@ -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))
+9 -12
View File
@@ -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 = [