mirror of
https://github.com/LorenEteval/Furious.git
synced 2026-10-11 16:58:11 +03:00
Add hysteria2 support
Signed-off-by: Loren Eteval <loren.eteval@proton.me>
This commit is contained in:
+127
-6
@@ -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 ''
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -58,6 +58,7 @@ class Protocol:
|
||||
VLESS = 'VLESS'
|
||||
Shadowsocks = 'shadowsocks'
|
||||
Hysteria1 = 'hysteria1'
|
||||
Hysteria2 = 'hysteria2'
|
||||
|
||||
|
||||
class StateContext:
|
||||
|
||||
Reference in New Issue
Block a user