Furious-GUI 0.2.4__tar.gz → 0.2.5.dev0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {Furious-GUI-0.2.4 → Furious-GUI-0.2.5.dev0}/Furious/Action/Connect.py +392 -48
- Furious-GUI-0.2.5.dev0/Furious/Action/EditConfiguration.py +32 -0
- Furious-GUI-0.2.5.dev0/Furious/Action/Exit.py +32 -0
- {Furious-GUI-0.2.4 → Furious-GUI-0.2.5.dev0}/Furious/Action/Export.py +22 -1
- {Furious-GUI-0.2.4 → Furious-GUI-0.2.5.dev0}/Furious/Action/Import.py +17 -0
- {Furious-GUI-0.2.4 → Furious-GUI-0.2.5.dev0}/Furious/Action/Language.py +17 -0
- {Furious-GUI-0.2.4 → Furious-GUI-0.2.5.dev0}/Furious/Action/Routing.py +17 -0
- Furious-GUI-0.2.5.dev0/Furious/Action/Settings.py +165 -0
- Furious-GUI-0.2.5.dev0/Furious/Action/__init__.py +24 -0
- {Furious-GUI-0.2.4 → Furious-GUI-0.2.5.dev0}/Furious/Core/Configuration.py +17 -0
- {Furious-GUI-0.2.4 → Furious-GUI-0.2.5.dev0}/Furious/Core/Core.py +33 -14
- {Furious-GUI-0.2.4 → Furious-GUI-0.2.5.dev0}/Furious/Core/Intellisense.py +17 -0
- {Furious-GUI-0.2.4 → Furious-GUI-0.2.5.dev0}/Furious/Core/TorRelay.py +60 -19
- Furious-GUI-0.2.5.dev0/Furious/Core/Tun2socks.py +67 -0
- Furious-GUI-0.2.5.dev0/Furious/Core/__init__.py +17 -0
- {Furious-GUI-0.2.4 → Furious-GUI-0.2.5.dev0}/Furious/Gui/Action.py +17 -0
- Furious-GUI-0.2.5.dev0/Furious/Gui/Icon.py +25 -0
- Furious-GUI-0.2.5.dev0/Furious/Gui/__init__.py +17 -0
- {Furious-GUI-0.2.4 → Furious-GUI-0.2.5.dev0}/Furious/Utility/Constants.py +36 -0
- {Furious-GUI-0.2.4 → Furious-GUI-0.2.5.dev0}/Furious/Utility/Process.py +17 -0
- {Furious-GUI-0.2.4 → Furious-GUI-0.2.5.dev0}/Furious/Utility/Proxy.py +19 -4
- {Furious-GUI-0.2.4 → Furious-GUI-0.2.5.dev0}/Furious/Utility/Resources.py +331 -32
- Furious-GUI-0.2.5.dev0/Furious/Utility/RoutingTable.py +239 -0
- {Furious-GUI-0.2.4 → Furious-GUI-0.2.5.dev0}/Furious/Utility/Settings.py +19 -0
- {Furious-GUI-0.2.4 → Furious-GUI-0.2.5.dev0}/Furious/Utility/StartupOnBoot.py +17 -0
- {Furious-GUI-0.2.4 → Furious-GUI-0.2.5.dev0}/Furious/Utility/Theme.py +17 -0
- {Furious-GUI-0.2.4 → Furious-GUI-0.2.5.dev0}/Furious/Utility/Translator.py +85 -12
- {Furious-GUI-0.2.4 → Furious-GUI-0.2.5.dev0}/Furious/Utility/Utility.py +56 -2
- Furious-GUI-0.2.5.dev0/Furious/Utility/__init__.py +17 -0
- Furious-GUI-0.2.5.dev0/Furious/Version.py +18 -0
- {Furious-GUI-0.2.4 → Furious-GUI-0.2.5.dev0}/Furious/Widget/Application.py +19 -2
- {Furious-GUI-0.2.4 → Furious-GUI-0.2.5.dev0}/Furious/Widget/AssetViewer.py +27 -0
- {Furious-GUI-0.2.4 → Furious-GUI-0.2.5.dev0}/Furious/Widget/ConnectingProgressBar.py +17 -0
- {Furious-GUI-0.2.4 → Furious-GUI-0.2.5.dev0}/Furious/Widget/EditConfiguration.py +48 -17
- {Furious-GUI-0.2.4 → Furious-GUI-0.2.5.dev0}/Furious/Widget/EditRouting.py +42 -3
- {Furious-GUI-0.2.4 → Furious-GUI-0.2.5.dev0}/Furious/Widget/ExportQRCode.py +17 -0
- {Furious-GUI-0.2.4 → Furious-GUI-0.2.5.dev0}/Furious/Widget/IndentSpinBox.py +17 -0
- {Furious-GUI-0.2.4 → Furious-GUI-0.2.5.dev0}/Furious/Widget/LogViewer.py +17 -0
- {Furious-GUI-0.2.4 → Furious-GUI-0.2.5.dev0}/Furious/Widget/SystemTrayIcon.py +18 -1
- {Furious-GUI-0.2.4 → Furious-GUI-0.2.5.dev0}/Furious/Widget/TorRelaySettings.py +35 -2
- {Furious-GUI-0.2.4 → Furious-GUI-0.2.5.dev0}/Furious/Widget/Widget.py +29 -6
- Furious-GUI-0.2.5.dev0/Furious/Widget/__init__.py +17 -0
- Furious-GUI-0.2.5.dev0/Furious/__init__.py +18 -0
- {Furious-GUI-0.2.4 → Furious-GUI-0.2.5.dev0}/Furious/__main__.py +17 -0
- {Furious-GUI-0.2.4 → Furious-GUI-0.2.5.dev0/Furious_GUI.egg-info}/PKG-INFO +33 -51
- {Furious-GUI-0.2.4 → Furious-GUI-0.2.5.dev0}/Furious_GUI.egg-info/SOURCES.txt +2 -0
- {Furious-GUI-0.2.4 → Furious-GUI-0.2.5.dev0}/Furious_GUI.egg-info/requires.txt +1 -0
- {Furious-GUI-0.2.4/Furious_GUI.egg-info → Furious-GUI-0.2.5.dev0}/PKG-INFO +33 -51
- {Furious-GUI-0.2.4 → Furious-GUI-0.2.5.dev0}/README.md +32 -50
- {Furious-GUI-0.2.4 → Furious-GUI-0.2.5.dev0}/setup.py +18 -0
- Furious-GUI-0.2.4/Furious/Action/EditConfiguration.py +0 -15
- Furious-GUI-0.2.4/Furious/Action/Exit.py +0 -15
- Furious-GUI-0.2.4/Furious/Action/Settings.py +0 -80
- Furious-GUI-0.2.4/Furious/Action/__init__.py +0 -7
- Furious-GUI-0.2.4/Furious/Core/__init__.py +0 -0
- Furious-GUI-0.2.4/Furious/Gui/Icon.py +0 -8
- Furious-GUI-0.2.4/Furious/Gui/__init__.py +0 -0
- Furious-GUI-0.2.4/Furious/Utility/__init__.py +0 -0
- Furious-GUI-0.2.4/Furious/Version.py +0 -1
- Furious-GUI-0.2.4/Furious/Widget/__init__.py +0 -0
- Furious-GUI-0.2.4/Furious/__init__.py +0 -1
- {Furious-GUI-0.2.4 → Furious-GUI-0.2.5.dev0}/Furious/Data/font/CascadiaMono +0 -0
- {Furious-GUI-0.2.4 → Furious-GUI-0.2.5.dev0}/Furious/Data/font/LICENSE +0 -0
- {Furious-GUI-0.2.4 → Furious-GUI-0.2.5.dev0}/Furious/Data/hysteria/bypass-Iran.acl +0 -0
- {Furious-GUI-0.2.4 → Furious-GUI-0.2.5.dev0}/Furious/Data/hysteria/bypass-mainland-China.acl +0 -0
- {Furious-GUI-0.2.4 → Furious-GUI-0.2.5.dev0}/Furious/Data/hysteria/country.mmdb +0 -0
- {Furious-GUI-0.2.4 → Furious-GUI-0.2.5.dev0}/Furious/Data/xray/geoip.dat +0 -0
- {Furious-GUI-0.2.4 → Furious-GUI-0.2.5.dev0}/Furious/Data/xray/geosite.dat +0 -0
- {Furious-GUI-0.2.4 → Furious-GUI-0.2.5.dev0}/Furious/Data/xray/iran.dat +0 -0
- {Furious-GUI-0.2.4 → Furious-GUI-0.2.5.dev0}/Furious_GUI.egg-info/dependency_links.txt +0 -0
- {Furious-GUI-0.2.4 → Furious-GUI-0.2.5.dev0}/Furious_GUI.egg-info/entry_points.txt +0 -0
- {Furious-GUI-0.2.4 → Furious-GUI-0.2.5.dev0}/Furious_GUI.egg-info/top_level.txt +0 -0
- {Furious-GUI-0.2.4 → Furious-GUI-0.2.5.dev0}/LICENSE +0 -0
- {Furious-GUI-0.2.4 → Furious-GUI-0.2.5.dev0}/setup.cfg +0 -0
|
@@ -1,5 +1,23 @@
|
|
|
1
|
+
# Copyright (C) 2023 Loren Eteval <loren.eteval@proton.me>
|
|
2
|
+
#
|
|
3
|
+
# This file is part of Furious.
|
|
4
|
+
#
|
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
|
6
|
+
# it under the terms of the GNU General Public License as published by
|
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
# (at your option) any later version.
|
|
9
|
+
#
|
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
# GNU General Public License for more details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU General Public License
|
|
16
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
1
18
|
from Furious.Core.Core import XrayCore, Hysteria
|
|
2
19
|
from Furious.Core.TorRelay import TorRelay
|
|
20
|
+
from Furious.Core.Tun2socks import Tun2socks
|
|
3
21
|
from Furious.Core.Intellisense import Intellisense
|
|
4
22
|
from Furious.Core.Configuration import Configuration
|
|
5
23
|
from Furious.Action.Routing import BUILTIN_ROUTING_TABLE, BUILTIN_ROUTING
|
|
@@ -8,18 +26,29 @@ from Furious.Widget.ConnectingProgressBar import ConnectingProgressBar
|
|
|
8
26
|
from Furious.Widget.Widget import MessageBox
|
|
9
27
|
from Furious.Utility.Constants import (
|
|
10
28
|
APP,
|
|
29
|
+
PLATFORM,
|
|
11
30
|
APPLICATION_NAME,
|
|
12
31
|
PROXY_SERVER_BYPASS,
|
|
32
|
+
APPLICATION_TUN_DEVICE_NAME,
|
|
33
|
+
APPLICATION_TUN_NETWORK_INTERFACE_NAME,
|
|
34
|
+
APPLICATION_TUN_IP_ADDRESS,
|
|
35
|
+
APPLICATION_TUN_GATEWAY_ADDRESS,
|
|
13
36
|
DEFAULT_TOR_HTTPS_PORT,
|
|
37
|
+
DEFAULT_TOR_SOCKS_PORT,
|
|
38
|
+
DEFAULT_TOR_RELAY_ESTABLISH_TIMEOUT,
|
|
14
39
|
)
|
|
15
40
|
from Furious.Utility.Utility import (
|
|
16
41
|
Switch,
|
|
17
42
|
SupportConnectedCallback,
|
|
18
43
|
bootstrapIcon,
|
|
19
44
|
getAbsolutePath,
|
|
45
|
+
isValidIPAddress,
|
|
46
|
+
isAdministrator,
|
|
47
|
+
isVPNMode,
|
|
20
48
|
)
|
|
21
49
|
from Furious.Utility.Translator import gettext as _
|
|
22
50
|
from Furious.Utility.Proxy import Proxy
|
|
51
|
+
from Furious.Utility.RoutingTable import RoutingTable
|
|
23
52
|
|
|
24
53
|
from PySide6 import QtCore
|
|
25
54
|
from PySide6.QtTest import QTest
|
|
@@ -37,7 +66,11 @@ import logging
|
|
|
37
66
|
logger = logging.getLogger(__name__)
|
|
38
67
|
|
|
39
68
|
|
|
40
|
-
class
|
|
69
|
+
class HttpsProxyServerError(Exception):
|
|
70
|
+
pass
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class SocksProxyServerError(Exception):
|
|
41
74
|
pass
|
|
42
75
|
|
|
43
76
|
|
|
@@ -55,9 +88,10 @@ class ConnectAction(Action):
|
|
|
55
88
|
self.configurationIcingBox = MessageBox()
|
|
56
89
|
self.configurationErrorBox = MessageBox()
|
|
57
90
|
self.configurationTampered = MessageBox()
|
|
58
|
-
self.
|
|
91
|
+
self.configurationProxyErr = MessageBox()
|
|
59
92
|
|
|
60
|
-
self.
|
|
93
|
+
self.httpsProxyServer = ''
|
|
94
|
+
self.socksProxyServer = ''
|
|
61
95
|
|
|
62
96
|
self.networkAccessManager = QNetworkAccessManager(parent=self)
|
|
63
97
|
self.networkReply = None
|
|
@@ -89,6 +123,7 @@ class ConnectAction(Action):
|
|
|
89
123
|
|
|
90
124
|
self.XrayCore = XrayCore()
|
|
91
125
|
self.Hysteria = Hysteria()
|
|
126
|
+
self.Tun2socks = Tun2socks()
|
|
92
127
|
self.TorRelay = TorRelay()
|
|
93
128
|
|
|
94
129
|
def XrayCoreExitCallback(self, exitcode):
|
|
@@ -97,6 +132,10 @@ class ConnectAction(Action):
|
|
|
97
132
|
assert self.coreRunning
|
|
98
133
|
assert self.coreName == XrayCore.name()
|
|
99
134
|
|
|
135
|
+
if exitcode == XrayCore.ExitCode.SystemShuttingDown:
|
|
136
|
+
# System shutting down. Do nothing
|
|
137
|
+
return
|
|
138
|
+
|
|
100
139
|
if exitcode == XrayCore.ExitCode.ConfigurationError:
|
|
101
140
|
if not self.isConnecting():
|
|
102
141
|
# Protect connecting action. Mandatory
|
|
@@ -142,6 +181,10 @@ class ConnectAction(Action):
|
|
|
142
181
|
assert self.coreRunning
|
|
143
182
|
assert self.coreName == Hysteria.name()
|
|
144
183
|
|
|
184
|
+
if exitcode == Hysteria.ExitCode.SystemShuttingDown:
|
|
185
|
+
# System shutting down. Do nothing
|
|
186
|
+
return
|
|
187
|
+
|
|
145
188
|
if exitcode == Hysteria.ExitCode.ConfigurationError:
|
|
146
189
|
if not self.isConnecting():
|
|
147
190
|
# Protect connecting action. Mandatory
|
|
@@ -181,7 +224,27 @@ class ConnectAction(Action):
|
|
|
181
224
|
f'{Hysteria.name()}: {_("Core terminated unexpectedly")}'
|
|
182
225
|
)
|
|
183
226
|
|
|
227
|
+
def Tun2socksExitCallback(self, exitcode):
|
|
228
|
+
if exitcode == Tun2socks.ExitCode.SystemShuttingDown:
|
|
229
|
+
# System shutting down. Do nothing
|
|
230
|
+
return
|
|
231
|
+
|
|
232
|
+
if not self.isConnecting():
|
|
233
|
+
# Protect connecting action. Mandatory
|
|
234
|
+
self.disconnectAction(
|
|
235
|
+
f'{Tun2socks.name()}: {_("Core terminated unexpectedly")}'
|
|
236
|
+
)
|
|
237
|
+
else:
|
|
238
|
+
self.coreRunning = False
|
|
239
|
+
self.disconnectReason = (
|
|
240
|
+
f'{Tun2socks.name()}: {_("Core terminated unexpectedly")}'
|
|
241
|
+
)
|
|
242
|
+
|
|
184
243
|
def stopCore(self):
|
|
244
|
+
self.stopTun2socks()
|
|
245
|
+
|
|
246
|
+
self.TorRelay.stop()
|
|
247
|
+
|
|
185
248
|
self.XrayCore.registerExitCallback(None)
|
|
186
249
|
self.Hysteria.registerExitCallback(None)
|
|
187
250
|
|
|
@@ -189,8 +252,6 @@ class ConnectAction(Action):
|
|
|
189
252
|
self.XrayCore.stop()
|
|
190
253
|
self.Hysteria.stop()
|
|
191
254
|
|
|
192
|
-
self.TorRelay.stop()
|
|
193
|
-
|
|
194
255
|
self.coreRunning = False
|
|
195
256
|
|
|
196
257
|
def showConnectingProgressBar(self):
|
|
@@ -225,6 +286,13 @@ class ConnectAction(Action):
|
|
|
225
286
|
|
|
226
287
|
APP().tray.RoutingAction.setDisabled(value)
|
|
227
288
|
|
|
289
|
+
if (PLATFORM == 'Windows' or PLATFORM == 'Darwin') and isAdministrator():
|
|
290
|
+
VPNModeAction = APP().tray.SettingsAction.getVPNModeAction()
|
|
291
|
+
|
|
292
|
+
assert VPNModeAction is not None
|
|
293
|
+
|
|
294
|
+
VPNModeAction.setDisabled(value)
|
|
295
|
+
|
|
228
296
|
def setConnectingStatus(self, showProgressBar=True):
|
|
229
297
|
if showProgressBar:
|
|
230
298
|
self.showConnectingProgressBar()
|
|
@@ -267,7 +335,8 @@ class ConnectAction(Action):
|
|
|
267
335
|
APP().Connect = Switch.OFF
|
|
268
336
|
APP().tray.setPlainIcon()
|
|
269
337
|
|
|
270
|
-
self.
|
|
338
|
+
self.httpsProxyServer = ''
|
|
339
|
+
self.socksProxyServer = ''
|
|
271
340
|
|
|
272
341
|
self.coreName = ''
|
|
273
342
|
self.coreText = ''
|
|
@@ -325,21 +394,29 @@ class ConnectAction(Action):
|
|
|
325
394
|
# Show the MessageBox and wait for user to close it
|
|
326
395
|
self.configurationIcingBox.exec()
|
|
327
396
|
|
|
328
|
-
def
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
self.
|
|
397
|
+
def errorProxyConf(self, proxyType):
|
|
398
|
+
assert proxyType == 'http' or proxyType == 'socks'
|
|
399
|
+
|
|
400
|
+
self.configurationProxyErr.setIcon(MessageBox.Icon.Critical)
|
|
401
|
+
self.configurationProxyErr.setWindowTitle(_('Unable to connect'))
|
|
402
|
+
self.configurationProxyErr.setText(
|
|
332
403
|
_(
|
|
333
|
-
f'{APPLICATION_NAME} cannot find any valid
|
|
404
|
+
f'{APPLICATION_NAME} cannot find any valid {proxyType} proxy '
|
|
334
405
|
f'endpoint in your server configuration.'
|
|
335
406
|
)
|
|
336
407
|
)
|
|
337
|
-
self.
|
|
408
|
+
self.configurationProxyErr.setInformativeText(
|
|
338
409
|
_('Please complete your server configuration.')
|
|
339
410
|
)
|
|
340
411
|
|
|
341
412
|
# Show the MessageBox and wait for user to close it
|
|
342
|
-
self.
|
|
413
|
+
self.configurationProxyErr.exec()
|
|
414
|
+
|
|
415
|
+
def errorHttpsProxyConf(self):
|
|
416
|
+
self.errorProxyConf('http')
|
|
417
|
+
|
|
418
|
+
def errorSocksProxyConf(self):
|
|
419
|
+
self.errorProxyConf('socks')
|
|
343
420
|
|
|
344
421
|
@property
|
|
345
422
|
def torRelayStorageObj(self):
|
|
@@ -347,8 +424,8 @@ class ConnectAction(Action):
|
|
|
347
424
|
return APP().torRelaySettingsWidget.StorageObj
|
|
348
425
|
|
|
349
426
|
def configureCore(self):
|
|
350
|
-
def
|
|
351
|
-
# Validate proxy server
|
|
427
|
+
def validateHttpsProxyServer(server):
|
|
428
|
+
# Validate https proxy server
|
|
352
429
|
try:
|
|
353
430
|
host, port = server.split(':')
|
|
354
431
|
|
|
@@ -358,11 +435,30 @@ class ConnectAction(Action):
|
|
|
358
435
|
# Any non-exit exceptions
|
|
359
436
|
|
|
360
437
|
self.reset()
|
|
361
|
-
self.
|
|
438
|
+
self.errorHttpsProxyConf()
|
|
362
439
|
|
|
363
440
|
return False
|
|
364
441
|
else:
|
|
365
|
-
self.
|
|
442
|
+
self.httpsProxyServer = server
|
|
443
|
+
|
|
444
|
+
return True
|
|
445
|
+
|
|
446
|
+
def validateSocksProxyServer(server):
|
|
447
|
+
# Validate socks proxy server
|
|
448
|
+
try:
|
|
449
|
+
host, port = server.split(':')
|
|
450
|
+
|
|
451
|
+
if int(port) < 0 or int(port) > 65535:
|
|
452
|
+
raise ValueError
|
|
453
|
+
except Exception:
|
|
454
|
+
# Any non-exit exceptions
|
|
455
|
+
|
|
456
|
+
self.reset()
|
|
457
|
+
self.errorSocksProxyConf()
|
|
458
|
+
|
|
459
|
+
return False
|
|
460
|
+
else:
|
|
461
|
+
self.socksProxyServer = server
|
|
366
462
|
|
|
367
463
|
return True
|
|
368
464
|
|
|
@@ -374,34 +470,69 @@ class ConnectAction(Action):
|
|
|
374
470
|
self.Hysteria.registerExitCallback(
|
|
375
471
|
lambda exitcode: self.HysteriaExitCallback(exitcode)
|
|
376
472
|
)
|
|
473
|
+
self.Tun2socks.registerExitCallback(
|
|
474
|
+
lambda exitcode: self.Tun2socksExitCallback(exitcode)
|
|
475
|
+
)
|
|
377
476
|
|
|
378
|
-
|
|
477
|
+
httpsProxyServer = None
|
|
478
|
+
socksProxyServer = None
|
|
379
479
|
|
|
380
480
|
if Intellisense.getCoreType(self.coreJSON) == XrayCore.name():
|
|
381
481
|
# Assuming is XrayCore configuration
|
|
382
|
-
|
|
383
|
-
|
|
482
|
+
httpsProxyHost = None
|
|
483
|
+
httpsProxyPort = None
|
|
484
|
+
socksProxyHost = None
|
|
485
|
+
socksProxyPort = None
|
|
486
|
+
|
|
487
|
+
if isVPNMode():
|
|
488
|
+
# Check socks inbound is valid
|
|
489
|
+
try:
|
|
490
|
+
for inbound in self.coreJSON['inbounds']:
|
|
491
|
+
if inbound['protocol'] == 'socks':
|
|
492
|
+
socksProxyHost = inbound['listen']
|
|
493
|
+
socksProxyPort = inbound['port']
|
|
494
|
+
|
|
495
|
+
# Note: If there are multiple socks inbounds
|
|
496
|
+
# satisfied, the first one will be chosen.
|
|
497
|
+
break
|
|
498
|
+
|
|
499
|
+
if socksProxyHost is None or socksProxyPort is None:
|
|
500
|
+
# No HTTP proxy endpoint configured
|
|
501
|
+
raise SocksProxyServerError
|
|
502
|
+
|
|
503
|
+
socksProxyServer = f'{socksProxyHost}:{socksProxyPort}'
|
|
504
|
+
except (KeyError, SocksProxyServerError):
|
|
505
|
+
self.reset()
|
|
506
|
+
self.errorSocksProxyConf()
|
|
507
|
+
|
|
508
|
+
# Return to caller
|
|
509
|
+
return XrayCore.name()
|
|
510
|
+
else:
|
|
511
|
+
# Validate socks proxy server
|
|
512
|
+
if not validateSocksProxyServer(socksProxyServer):
|
|
513
|
+
# Return to caller
|
|
514
|
+
return XrayCore.name()
|
|
384
515
|
|
|
385
516
|
try:
|
|
386
517
|
for inbound in self.coreJSON['inbounds']:
|
|
387
518
|
if inbound['protocol'] == 'http':
|
|
388
|
-
|
|
389
|
-
|
|
519
|
+
httpsProxyHost = inbound['listen']
|
|
520
|
+
httpsProxyPort = inbound['port']
|
|
390
521
|
|
|
391
522
|
# Note: If there are multiple http inbounds
|
|
392
523
|
# satisfied, the first one will be chosen.
|
|
393
524
|
break
|
|
394
525
|
|
|
395
|
-
if
|
|
526
|
+
if httpsProxyHost is None or httpsProxyPort is None:
|
|
396
527
|
# No HTTP proxy endpoint configured
|
|
397
|
-
raise
|
|
528
|
+
raise HttpsProxyServerError
|
|
398
529
|
|
|
399
|
-
|
|
400
|
-
except (KeyError,
|
|
530
|
+
httpsProxyServer = f'{httpsProxyHost}:{httpsProxyPort}'
|
|
531
|
+
except (KeyError, HttpsProxyServerError):
|
|
401
532
|
self.reset()
|
|
402
|
-
self.
|
|
533
|
+
self.errorHttpsProxyConf()
|
|
403
534
|
else:
|
|
404
|
-
if
|
|
535
|
+
if validateHttpsProxyServer(httpsProxyServer):
|
|
405
536
|
routing = APP().Routing
|
|
406
537
|
|
|
407
538
|
logger.info(f'core {XrayCore.name()} configured')
|
|
@@ -463,7 +594,6 @@ class ConnectAction(Action):
|
|
|
463
594
|
logger.error('find Tor CLI in path failed')
|
|
464
595
|
|
|
465
596
|
self.coreRunning = False
|
|
466
|
-
|
|
467
597
|
self.disconnectReason = (
|
|
468
598
|
f'{XrayCore.name()}: {_("Cannot find Tor CLI in PATH")}'
|
|
469
599
|
)
|
|
@@ -502,26 +632,57 @@ class ConnectAction(Action):
|
|
|
502
632
|
self.coreJSON, ensure_ascii=False, escape_forward_slashes=False
|
|
503
633
|
)
|
|
504
634
|
# Start core
|
|
505
|
-
self.
|
|
635
|
+
if self.coreJSON:
|
|
636
|
+
self.XrayCore.start(self.coreText)
|
|
637
|
+
|
|
638
|
+
if routing == 'Route My Traffic Through Tor':
|
|
639
|
+
# Must start Tor Relay first since it will redirect proxy for us
|
|
640
|
+
self.startTorRelay(XrayCore.name(), httpsProxyServer)
|
|
506
641
|
|
|
507
|
-
|
|
508
|
-
|
|
642
|
+
if isVPNMode():
|
|
643
|
+
if PLATFORM == 'Windows' or PLATFORM == 'Darwin':
|
|
644
|
+
# Currently VPN Mode is only supported on Windows and macOS
|
|
645
|
+
self.startTun2socks()
|
|
646
|
+
else:
|
|
647
|
+
# Fast fail
|
|
648
|
+
self.XrayCore.start(self.coreText, waitTime=1000)
|
|
509
649
|
|
|
510
650
|
return XrayCore.name()
|
|
511
651
|
|
|
512
652
|
if Intellisense.getCoreType(self.coreJSON) == Hysteria.name():
|
|
513
653
|
# Assuming is Hysteria configuration
|
|
654
|
+
|
|
655
|
+
if isVPNMode():
|
|
656
|
+
# Check socks inbound is valid
|
|
657
|
+
try:
|
|
658
|
+
socksProxyServer = self.coreJSON['socks5']['listen']
|
|
659
|
+
|
|
660
|
+
if socksProxyServer is None:
|
|
661
|
+
# No SOCKS proxy endpoint configured
|
|
662
|
+
raise SocksProxyServerError
|
|
663
|
+
except (KeyError, SocksProxyServerError):
|
|
664
|
+
self.reset()
|
|
665
|
+
self.errorSocksProxyConf()
|
|
666
|
+
|
|
667
|
+
# Return to caller
|
|
668
|
+
return Hysteria.name()
|
|
669
|
+
else:
|
|
670
|
+
# Validate socks proxy server
|
|
671
|
+
if not validateSocksProxyServer(socksProxyServer):
|
|
672
|
+
# Return to caller
|
|
673
|
+
return Hysteria.name()
|
|
674
|
+
|
|
514
675
|
try:
|
|
515
|
-
|
|
676
|
+
httpsProxyServer = self.coreJSON['http']['listen']
|
|
516
677
|
|
|
517
|
-
if
|
|
678
|
+
if httpsProxyServer is None:
|
|
518
679
|
# No HTTP proxy endpoint configured
|
|
519
|
-
raise
|
|
520
|
-
except (KeyError,
|
|
680
|
+
raise HttpsProxyServerError
|
|
681
|
+
except (KeyError, HttpsProxyServerError):
|
|
521
682
|
self.reset()
|
|
522
|
-
self.
|
|
683
|
+
self.errorHttpsProxyConf()
|
|
523
684
|
else:
|
|
524
|
-
if
|
|
685
|
+
if validateHttpsProxyServer(httpsProxyServer):
|
|
525
686
|
self.coreRunning = True
|
|
526
687
|
|
|
527
688
|
routing = APP().Routing
|
|
@@ -556,12 +717,11 @@ class ConnectAction(Action):
|
|
|
556
717
|
|
|
557
718
|
self.Hysteria.start(self.coreText, '', '')
|
|
558
719
|
|
|
559
|
-
self.startTorRelay(Hysteria.name(),
|
|
720
|
+
self.startTorRelay(Hysteria.name(), httpsProxyServer)
|
|
560
721
|
else:
|
|
561
722
|
logger.error('find Tor CLI in path failed')
|
|
562
723
|
|
|
563
724
|
self.coreRunning = False
|
|
564
|
-
|
|
565
725
|
self.disconnectReason = (
|
|
566
726
|
f'{Hysteria.name()}: {_("Cannot find Tor CLI in PATH")}'
|
|
567
727
|
)
|
|
@@ -601,26 +761,206 @@ class ConnectAction(Action):
|
|
|
601
761
|
)
|
|
602
762
|
|
|
603
763
|
# Fast fail
|
|
604
|
-
self.
|
|
764
|
+
self.coreJSON = {}
|
|
765
|
+
self.coreText = ''
|
|
766
|
+
|
|
767
|
+
self.Hysteria.start(self.coreText, '', '', waitTime=1000)
|
|
768
|
+
|
|
769
|
+
if self.coreJSON:
|
|
770
|
+
if isVPNMode():
|
|
771
|
+
if PLATFORM == 'Windows' or PLATFORM == 'Darwin':
|
|
772
|
+
# Currently VPN Mode is only supported on Windows and macOS
|
|
773
|
+
self.startTun2socks()
|
|
605
774
|
|
|
606
775
|
return Hysteria.name()
|
|
607
776
|
|
|
608
777
|
# No matching core
|
|
609
778
|
return ''
|
|
610
779
|
|
|
780
|
+
def startTun2socks(self, successCallback=None):
|
|
781
|
+
if not self.coreRunning:
|
|
782
|
+
# Core has exited. Do nothing
|
|
783
|
+
return
|
|
784
|
+
|
|
785
|
+
defaultGateway = list(
|
|
786
|
+
# Filter TUN Gateway
|
|
787
|
+
filter(
|
|
788
|
+
lambda x: x != APPLICATION_TUN_GATEWAY_ADDRESS,
|
|
789
|
+
RoutingTable.getDefaultGatewayAddress(),
|
|
790
|
+
)
|
|
791
|
+
)
|
|
792
|
+
|
|
793
|
+
if len(defaultGateway) != 1:
|
|
794
|
+
logger.error(f'found multiple gateway addresses: {defaultGateway}')
|
|
795
|
+
|
|
796
|
+
self.coreRunning = False
|
|
797
|
+
self.disconnectReason = (
|
|
798
|
+
_('Unable to connect') + ': ' + _('Multiple gateway addresses found')
|
|
799
|
+
)
|
|
800
|
+
|
|
801
|
+
return
|
|
802
|
+
|
|
803
|
+
coreAddr = Intellisense.getCoreAddr(self.coreJSON)
|
|
804
|
+
|
|
805
|
+
if not coreAddr:
|
|
806
|
+
logger.error(f'invalid server address: {coreAddr}')
|
|
807
|
+
|
|
808
|
+
self.coreRunning = False
|
|
809
|
+
self.disconnectReason = _('Server address invalid') + f': {coreAddr}'
|
|
810
|
+
|
|
811
|
+
return
|
|
812
|
+
|
|
813
|
+
def start():
|
|
814
|
+
assert RoutingTable.Relations
|
|
815
|
+
|
|
816
|
+
if PLATFORM == 'Darwin':
|
|
817
|
+
for source in [
|
|
818
|
+
*list(f'{2 ** (8 - x)}.0.0.0/{x}' for x in range(8, 0, -1)),
|
|
819
|
+
'198.18.0.0/15',
|
|
820
|
+
]:
|
|
821
|
+
RoutingTable.Relations.append(
|
|
822
|
+
[source, APPLICATION_TUN_GATEWAY_ADDRESS]
|
|
823
|
+
)
|
|
824
|
+
|
|
825
|
+
self.Tun2socks.start(
|
|
826
|
+
APPLICATION_TUN_DEVICE_NAME,
|
|
827
|
+
APPLICATION_TUN_NETWORK_INTERFACE_NAME,
|
|
828
|
+
'silent',
|
|
829
|
+
f'socks5://{self.socksProxyServer}',
|
|
830
|
+
'',
|
|
831
|
+
)
|
|
832
|
+
|
|
833
|
+
if PLATFORM == 'Windows':
|
|
834
|
+
RoutingTable.addRelations()
|
|
835
|
+
RoutingTable.setDeviceGatewayAddress(
|
|
836
|
+
APPLICATION_TUN_DEVICE_NAME,
|
|
837
|
+
APPLICATION_TUN_IP_ADDRESS,
|
|
838
|
+
APPLICATION_TUN_GATEWAY_ADDRESS,
|
|
839
|
+
)
|
|
840
|
+
|
|
841
|
+
if PLATFORM == 'Darwin':
|
|
842
|
+
RoutingTable.setDeviceGatewayAddress(
|
|
843
|
+
APPLICATION_TUN_DEVICE_NAME,
|
|
844
|
+
APPLICATION_TUN_IP_ADDRESS,
|
|
845
|
+
APPLICATION_TUN_GATEWAY_ADDRESS,
|
|
846
|
+
)
|
|
847
|
+
RoutingTable.addRelations()
|
|
848
|
+
|
|
849
|
+
if callable(successCallback):
|
|
850
|
+
successCallback()
|
|
851
|
+
|
|
852
|
+
if not isValidIPAddress(coreAddr):
|
|
853
|
+
logger.info(f'dns resolve uses proxy server {self.httpsProxyServer}')
|
|
854
|
+
|
|
855
|
+
# Checked. split should not throw exceptions
|
|
856
|
+
proxyHost, proxyPort = self.httpsProxyServer.split(':')
|
|
857
|
+
|
|
858
|
+
# Checked. int(proxyPort) should not throw exceptions
|
|
859
|
+
self.networkAccessManager.setProxy(
|
|
860
|
+
QNetworkProxy(
|
|
861
|
+
QNetworkProxy.ProxyType.HttpProxy, proxyHost, int(proxyPort)
|
|
862
|
+
)
|
|
863
|
+
)
|
|
864
|
+
|
|
865
|
+
request = QNetworkRequest(
|
|
866
|
+
QtCore.QUrl(f'https://cloudflare-dns.com/dns-query?name={coreAddr}')
|
|
867
|
+
)
|
|
868
|
+
request.setRawHeader('accept'.encode(), 'application/dns-json'.encode())
|
|
869
|
+
|
|
870
|
+
self.networkReply = self.networkAccessManager.get(request)
|
|
871
|
+
|
|
872
|
+
@QtCore.Slot()
|
|
873
|
+
def finishedCallback():
|
|
874
|
+
if self.networkReply is None:
|
|
875
|
+
# Interrupted externally. Do nothing
|
|
876
|
+
logger.info(f'dns resolve for {coreAddr} interrupted externally')
|
|
877
|
+
|
|
878
|
+
return
|
|
879
|
+
|
|
880
|
+
assert isinstance(self.networkReply, QNetworkReply)
|
|
881
|
+
|
|
882
|
+
if self.networkReply.error() != QNetworkReply.NetworkError.NoError:
|
|
883
|
+
logger.error(
|
|
884
|
+
f'DNS resolution for {coreAddr} failed. {self.networkReply.errorString()}'
|
|
885
|
+
)
|
|
886
|
+
|
|
887
|
+
self.coreRunning = False
|
|
888
|
+
self.disconnectReason = _('DNS resolution failed') + f': {coreAddr}'
|
|
889
|
+
# Reset reply
|
|
890
|
+
self.networkReply = None
|
|
891
|
+
else:
|
|
892
|
+
logger.info(f'dns resolve for {coreAddr} success')
|
|
893
|
+
|
|
894
|
+
replyObject = ujson.loads(self.networkReply.readAll().data())
|
|
895
|
+
|
|
896
|
+
# Reset reply
|
|
897
|
+
self.networkReply = None
|
|
898
|
+
|
|
899
|
+
for record in replyObject['Answer']:
|
|
900
|
+
address = record['data']
|
|
901
|
+
|
|
902
|
+
logger.info(f'{coreAddr} resolved to {address}')
|
|
903
|
+
|
|
904
|
+
RoutingTable.Relations.append([address, defaultGateway[0]])
|
|
905
|
+
|
|
906
|
+
start()
|
|
907
|
+
|
|
908
|
+
self.networkReply.finished.connect(finishedCallback)
|
|
909
|
+
else:
|
|
910
|
+
# Reset reply
|
|
911
|
+
self.networkReply = None
|
|
912
|
+
|
|
913
|
+
RoutingTable.Relations.append([coreAddr, defaultGateway[0]])
|
|
914
|
+
|
|
915
|
+
start()
|
|
916
|
+
|
|
917
|
+
self.waitForDNSResolutionIfNecessary()
|
|
918
|
+
|
|
919
|
+
def stopTun2socks(self):
|
|
920
|
+
self.Tun2socks.registerExitCallback(None)
|
|
921
|
+
self.Tun2socks.stop()
|
|
922
|
+
|
|
923
|
+
def waitForDNSResolutionIfNecessary(self, startCounter=0, timeout=10000, step=1):
|
|
924
|
+
# Wait for potentially DNS resolve in VPN Mode
|
|
925
|
+
if self.coreRunning and self.networkReply is not None:
|
|
926
|
+
logger.info('dns resolve in progress. Wait')
|
|
927
|
+
else:
|
|
928
|
+
return
|
|
929
|
+
|
|
930
|
+
while (
|
|
931
|
+
self.coreRunning
|
|
932
|
+
and startCounter < timeout
|
|
933
|
+
and self.networkReply is not None
|
|
934
|
+
):
|
|
935
|
+
QTest.qWait(step)
|
|
936
|
+
|
|
937
|
+
startCounter += step
|
|
938
|
+
|
|
939
|
+
if self.networkReply is not None:
|
|
940
|
+
if self.coreRunning:
|
|
941
|
+
logger.error('DNS resolution timeout')
|
|
942
|
+
|
|
943
|
+
self.coreRunning = False
|
|
944
|
+
self.disconnectReason = _('DNS resolution timeout')
|
|
945
|
+
|
|
946
|
+
# Reset reply
|
|
947
|
+
self.networkReply = None
|
|
948
|
+
|
|
611
949
|
def startTorRelay(self, core, proxyServer, startCounter=0, step=1):
|
|
612
950
|
# Redirect Proxy
|
|
613
|
-
self.
|
|
951
|
+
self.httpsProxyServer = f'127.0.0.1:{self.torRelayStorageObj.get("httpsTunnelPort", DEFAULT_TOR_HTTPS_PORT)}'
|
|
952
|
+
self.socksProxyServer = f'127.0.0.1:{self.torRelayStorageObj.get("socksTunnelPort", DEFAULT_TOR_SOCKS_PORT)}'
|
|
614
953
|
|
|
615
954
|
try:
|
|
616
955
|
timeout = 1000 * int(
|
|
617
|
-
self.torRelayStorageObj.get(
|
|
956
|
+
self.torRelayStorageObj.get(
|
|
957
|
+
'relayEstablishTimeout', DEFAULT_TOR_RELAY_ESTABLISH_TIMEOUT
|
|
958
|
+
)
|
|
618
959
|
)
|
|
619
960
|
except Exception:
|
|
620
961
|
# Any non-exit exceptions
|
|
621
962
|
|
|
622
|
-
|
|
623
|
-
timeout = 1000 * 15
|
|
963
|
+
timeout = 1000 * DEFAULT_TOR_RELAY_ESTABLISH_TIMEOUT
|
|
624
964
|
|
|
625
965
|
logger.info(f'{TorRelay.name()} bootstrap timeout is {timeout // 1000}s')
|
|
626
966
|
|
|
@@ -665,9 +1005,10 @@ class ConnectAction(Action):
|
|
|
665
1005
|
self.testTime += 1
|
|
666
1006
|
|
|
667
1007
|
logger.info(f'start connection test. Try: {selected}')
|
|
1008
|
+
logger.info(f'connection test uses proxy server {self.httpsProxyServer}')
|
|
668
1009
|
|
|
669
1010
|
# Checked. split should not throw exceptions
|
|
670
|
-
proxyHost, proxyPort = self.
|
|
1011
|
+
proxyHost, proxyPort = self.httpsProxyServer.split(':')
|
|
671
1012
|
|
|
672
1013
|
# Checked. int(proxyPort) should not throw exceptions
|
|
673
1014
|
self.networkAccessManager.setProxy(
|
|
@@ -795,6 +1136,9 @@ class ConnectAction(Action):
|
|
|
795
1136
|
# Connecting status
|
|
796
1137
|
self.setConnectingStatus(showProgressBar)
|
|
797
1138
|
|
|
1139
|
+
# Reset reply. Mandatory
|
|
1140
|
+
self.networkReply = None
|
|
1141
|
+
|
|
798
1142
|
# Configure connect
|
|
799
1143
|
self.coreName = self.configureCore()
|
|
800
1144
|
|
|
@@ -806,7 +1150,7 @@ class ConnectAction(Action):
|
|
|
806
1150
|
return
|
|
807
1151
|
|
|
808
1152
|
if not self.coreRunning:
|
|
809
|
-
# 1. No valid HTTP proxy endpoint. reset / disconnect has been called
|
|
1153
|
+
# 1. No valid HTTP/Socks proxy endpoint. reset / disconnect has been called
|
|
810
1154
|
|
|
811
1155
|
if self.isConnecting():
|
|
812
1156
|
# 2. Core has exited
|
|
@@ -816,7 +1160,7 @@ class ConnectAction(Action):
|
|
|
816
1160
|
return
|
|
817
1161
|
|
|
818
1162
|
try:
|
|
819
|
-
Proxy.set(self.
|
|
1163
|
+
Proxy.set(self.httpsProxyServer, PROXY_SERVER_BYPASS)
|
|
820
1164
|
except Exception:
|
|
821
1165
|
# Any non-exit exceptions
|
|
822
1166
|
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Copyright (C) 2023 Loren Eteval <loren.eteval@proton.me>
|
|
2
|
+
#
|
|
3
|
+
# This file is part of Furious.
|
|
4
|
+
#
|
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
|
6
|
+
# it under the terms of the GNU General Public License as published by
|
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
# (at your option) any later version.
|
|
9
|
+
#
|
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
# GNU General Public License for more details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU General Public License
|
|
16
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
from Furious.Gui.Action import Action
|
|
19
|
+
from Furious.Utility.Constants import APP
|
|
20
|
+
from Furious.Utility.Utility import bootstrapIcon
|
|
21
|
+
from Furious.Utility.Translator import gettext as _
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class EditConfigurationAction(Action):
|
|
25
|
+
def __init__(self):
|
|
26
|
+
super().__init__(
|
|
27
|
+
_('Edit Configuration...'),
|
|
28
|
+
icon=bootstrapIcon('pencil-square.svg'),
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
def triggeredCallback(self, checked):
|
|
32
|
+
APP().ServerWidget.show()
|