mirror of
https://github.com/LorenEteval/Furious.git
synced 2026-10-10 08:18:15 +03:00
Add Linux TUN support
Signed-off-by: Loren Eteval <loren.eteval@proton.me>
This commit is contained in:
+322
-254
@@ -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
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 = ''
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
Reference in New Issue
Block a user