Add hysteria2 support

Signed-off-by: Loren Eteval <loren.eteval@proton.me>
This commit is contained in:
Loren Eteval
2023-09-10 14:06:22 +08:00
parent 7c1b959035
commit 10c2e3baee
4 changed files with 213 additions and 17 deletions
+127 -6
View File
@@ -15,7 +15,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from Furious.Core.Core import XrayCore, Hysteria1
from Furious.Core.Core import XrayCore, Hysteria1, Hysteria2
from Furious.Core.TorRelay import TorRelay
from Furious.Core.Tun2socks import Tun2socks
from Furious.Core.Intellisense import Intellisense
@@ -123,6 +123,7 @@ class ConnectAction(Action):
self.XrayCore = XrayCore()
self.Hysteria1 = Hysteria1()
self.Hysteria2 = Hysteria2()
self.Tun2socks = Tun2socks()
self.TorRelay = TorRelay()
@@ -224,6 +225,55 @@ class ConnectAction(Action):
f'{Hysteria1.name()}: {_("Core terminated unexpectedly")}'
)
def Hysteria2ExitCallback(self, exitcode):
if self.coreName:
# If core is running
assert self.coreRunning
assert self.coreName == Hysteria2.name()
if exitcode == Hysteria2.ExitCode.SystemShuttingDown:
# System shutting down. Do nothing
return
if exitcode == Hysteria2.ExitCode.ConfigurationError:
if not self.isConnecting():
# Protect connecting action. Mandatory
return self.disconnectAction(
f'{Hysteria2.name()}: {_("Invalid server configuration")}'
)
else:
self.coreRunning = False
self.disconnectReason = (
f'{Hysteria2.name()}: {_("Invalid server configuration")}'
)
return
if exitcode == Hysteria2.ExitCode.ServerStartFailure:
if not self.isConnecting():
# Protect connecting action. Mandatory
return self.disconnectAction(
f'{Hysteria2.name()}: {_("Failed to start core")}'
)
else:
self.coreRunning = False
self.disconnectReason = (
f'{Hysteria2.name()}: {_("Failed to start core")}'
)
return
if not self.isConnecting():
# Protect connecting action. Mandatory
self.disconnectAction(
f'{Hysteria2.name()}: {_("Core terminated unexpectedly")}'
)
else:
self.coreRunning = False
self.disconnectReason = (
f'{Hysteria2.name()}: {_("Core terminated unexpectedly")}'
)
def Tun2socksExitCallback(self, exitcode):
if exitcode == Tun2socks.ExitCode.SystemShuttingDown:
# System shutting down. Do nothing
@@ -247,10 +297,12 @@ class ConnectAction(Action):
self.XrayCore.registerExitCallback(None)
self.Hysteria1.registerExitCallback(None)
self.Hysteria2.registerExitCallback(None)
# Stop any potentially running core
self.XrayCore.stop()
self.Hysteria1.stop()
self.Hysteria2.stop()
self.coreRunning = False
@@ -470,6 +522,9 @@ class ConnectAction(Action):
self.Hysteria1.registerExitCallback(
lambda exitcode: self.Hysteria1ExitCallback(exitcode)
)
self.Hysteria2.registerExitCallback(
lambda exitcode: self.Hysteria2ExitCallback(exitcode)
)
self.Tun2socks.registerExitCallback(
lambda exitcode: self.Tun2socksExitCallback(exitcode)
)
@@ -533,9 +588,6 @@ class ConnectAction(Action):
self.errorHttpsProxyConf()
else:
if validateHttpsProxyServer(httpsProxyServer):
routing = APP().Routing
logger.info(f'core {XrayCore.name()} configured')
def fixLoggingRelativePath(attr):
# Relative path fails if booting on start up
@@ -560,9 +612,13 @@ class ConnectAction(Action):
self.coreJSON['log'][attr] = fix
logger.info(f'core {XrayCore.name()} configured')
fixLoggingRelativePath('access')
fixLoggingRelativePath('error')
routing = APP().Routing
# Filter Route My Traffic Through Tor, Custom
if routing in list(
filter(
@@ -683,12 +739,12 @@ class ConnectAction(Action):
self.errorHttpsProxyConf()
else:
if validateHttpsProxyServer(httpsProxyServer):
logger.info(f'core {Hysteria1.name()} configured')
self.coreRunning = True
routing = APP().Routing
logger.info(f'core {Hysteria1.name()} configured')
# Filter Route My Traffic Through Tor, Global, Custom
if routing in list(
filter(
@@ -772,6 +828,71 @@ class ConnectAction(Action):
return Hysteria1.name()
if Intellisense.getCoreType(self.coreJSON) == Hysteria2.name():
# Assuming is Hysteria2 configuration
if isVPNMode():
# Check socks inbound is valid
try:
socksProxyServer = self.coreJSON['socks5']['listen']
if socksProxyServer is None:
# No SOCKS proxy endpoint configured
raise SocksProxyServerError
except (KeyError, SocksProxyServerError):
self.reset()
self.errorSocksProxyConf()
# Return to caller
return Hysteria2.name()
else:
# Validate socks proxy server
if not validateSocksProxyServer(socksProxyServer):
# Return to caller
return Hysteria2.name()
try:
httpsProxyServer = self.coreJSON['http']['listen']
if httpsProxyServer is None:
# No HTTP proxy endpoint configured
raise HttpsProxyServerError
except (KeyError, HttpsProxyServerError):
self.reset()
self.errorHttpsProxyConf()
else:
if validateHttpsProxyServer(httpsProxyServer):
logger.info(f'core {Hysteria2.name()} configured')
self.coreRunning = True
routing = APP().Routing
if routing == 'Route My Traffic Through Tor':
logger.info(f'routing is {routing}')
if TorRelay.checkIfExists():
logger.info(
f'find Tor CLI in path success. Version: {TorRelay.version()}'
)
self.Hysteria2.start(self.coreText)
self.startTorRelay(Hysteria2.name(), httpsProxyServer)
else:
logger.error('find Tor CLI in path failed')
self.coreRunning = False
self.disconnectReason = f'{Hysteria2.name()}: {_("Cannot find Tor CLI in PATH")}'
return Hysteria2.name()
else:
logger.info(f'routing is {routing}')
self.Hysteria2.start(self.coreText)
return Hysteria2.name()
# No matching core
return ''
+41
View File
@@ -216,3 +216,44 @@ class Hysteria1(Core):
def start(self, json, rule, mmdb, **kwargs):
super().start(target=startHysteria1, args=(json, rule, mmdb), **kwargs)
def startHysteria2(json):
try:
import hysteria2
except ImportError:
# Fake running process
while True:
pass
else:
hysteria2.startFromJSON(json)
class Hysteria2(Core):
class ExitCode:
ConfigurationError = 23
# Windows: 4294967295. Darwin, Linux: 255 (-1)
ServerStartFailure = 4294967295 if PLATFORM == 'Windows' else 255
# Windows shutting down
SystemShuttingDown = 0x40010004
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@staticmethod
def name():
return 'Hysteria2'
@staticmethod
def version():
try:
import hysteria2
return hysteria2.__version__
except Exception:
# Any non-exit exceptions
return '0.0.0'
def start(self, json, **kwargs):
super().start(target=startHysteria2, args=(json,), **kwargs)
+44 -11
View File
@@ -15,20 +15,50 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from Furious.Core.Core import XrayCore, Hysteria1
from Furious.Core.Core import XrayCore, Hysteria1, Hysteria2
from Furious.Utility.Utility import Protocol, protocolRepr
class Intellisense:
@staticmethod
def getCoreType(ob):
if ob.get('inbounds') is not None or ob.get('outbounds') is not None:
def hasField(field):
return ob.get(field) is not None
if hasField('inbounds') or hasField('outbounds'):
# Assuming is XrayCore
return XrayCore.name()
if ob.get('server') is not None:
# Assuming is Hysteria1. This behavior might be changed in the future
return Hysteria1.name()
if hasField('server'):
if (
hasField('protocol')
or hasField('up_mbps')
or hasField('down_mbps')
or hasField('auth_str')
or hasField('alpn')
or hasField('server_name')
or hasField('insecure')
or hasField('recv_window_conn')
or hasField('recv_window')
or isinstance(ob.get('obfs'), str)
or hasField('fast_open')
or hasField('lazy_start')
):
return Hysteria1.name()
elif (
hasField('tls')
or hasField('transport')
or hasField('quic')
or hasField('bandwidth')
or hasField('tcpForwarding')
or hasField('udpForwarding')
or hasField('tcpTProxy')
or hasField('udpTProxy')
or isinstance(ob.get('obfs'), dict)
or hasField('fastOpen')
or hasField('lazy')
):
return Hysteria2.name()
return ''
@@ -43,6 +73,9 @@ class Intellisense:
if Intellisense.getCoreType(ob) == Hysteria1.name():
return Protocol.Hysteria1
if Intellisense.getCoreType(ob) == Hysteria2.name():
return Protocol.Hysteria2
return ''
except Exception:
# Any non-exit exceptions
@@ -76,6 +109,9 @@ class Intellisense:
if Intellisense.getCoreType(ob) == Hysteria1.name():
return ob['server'].split(':')[0]
if Intellisense.getCoreType(ob) == Hysteria2.name():
return ob['server'].split(':')[0]
return ''
except Exception:
# Any non-exit exceptions
@@ -109,6 +145,9 @@ class Intellisense:
if Intellisense.getCoreType(ob) == Hysteria1.name():
return ob['server'].split(':')[1]
if Intellisense.getCoreType(ob) == Hysteria2.name():
return ob['server'].split(':')[1]
return ''
except Exception:
# Any non-exit exceptions
@@ -123,9 +162,6 @@ class Intellisense:
if outbound['tag'] == 'proxy':
return outbound['streamSettings']['network']
if Intellisense.getCoreType(ob) == Hysteria1.name():
return ''
return ''
except Exception:
# Any non-exit exceptions
@@ -140,9 +176,6 @@ class Intellisense:
if outbound['tag'] == 'proxy':
return outbound['streamSettings']['security']
if Intellisense.getCoreType(ob) == Hysteria1.name():
return ''
return ''
except Exception:
# Any non-exit exceptions
+1
View File
@@ -58,6 +58,7 @@ class Protocol:
VLESS = 'VLESS'
Shadowsocks = 'shadowsocks'
Hysteria1 = 'hysteria1'
Hysteria2 = 'hysteria2'
class StateContext: