proxynt 2.0.57__tar.gz → 2.0.58__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.
- {proxynt-2.0.57 → proxynt-2.0.58}/PKG-INFO +1 -1
- proxynt-2.0.58/client/heart_beat_task.py +74 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/client/tcp_forward_client.py +58 -211
- {proxynt-2.0.57 → proxynt-2.0.58}/client/udp_forward_client.py +52 -100
- {proxynt-2.0.57 → proxynt-2.0.58}/common/pool.py +5 -1
- {proxynt-2.0.57 → proxynt-2.0.58}/constant/system_constant.py +1 -1
- {proxynt-2.0.57 → proxynt-2.0.58}/proxynt.egg-info/PKG-INFO +1 -1
- {proxynt-2.0.57 → proxynt-2.0.58}/proxynt.egg-info/SOURCES.txt +0 -18
- {proxynt-2.0.57 → proxynt-2.0.58}/run_client.py +165 -237
- proxynt-2.0.57/build/lib/proxynt/common/websocket2/compliance/autobahn-test-report-Feb-03-2021.html +0 -5923
- proxynt-2.0.57/build/lib/proxynt/server/template/base.html +0 -38
- proxynt-2.0.57/build/lib/proxynt/server/template/css/fonts/element-icons.woff +0 -0
- proxynt-2.0.57/build/lib/proxynt/server/template/css/index.css +0 -1
- proxynt-2.0.57/build/lib/proxynt/server/template/ele_index.html +0 -624
- proxynt-2.0.57/build/lib/proxynt/server/template/js/axios.min.js +0 -3
- proxynt-2.0.57/build/lib/proxynt/server/template/js/index.js +0 -1
- proxynt-2.0.57/build/lib/proxynt/server/template/js/vue.min.js +0 -6
- proxynt-2.0.57/build/lib/proxynt/server/template/login.html +0 -54
- proxynt-2.0.57/client/heart_beat_task.py +0 -82
- proxynt-2.0.57/common/websocket2/compliance/autobahn-test-report-Feb-03-2021.html +0 -5923
- proxynt-2.0.57/server/template/base.html +0 -38
- proxynt-2.0.57/server/template/css/fonts/element-icons.woff +0 -0
- proxynt-2.0.57/server/template/css/index.css +0 -1
- proxynt-2.0.57/server/template/ele_index.html +0 -624
- proxynt-2.0.57/server/template/js/axios.min.js +0 -3
- proxynt-2.0.57/server/template/js/index.js +0 -1
- proxynt-2.0.57/server/template/js/vue.min.js +0 -6
- proxynt-2.0.57/server/template/login.html +0 -54
- {proxynt-2.0.57 → proxynt-2.0.58}/LICENSE +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/MANIFEST.in +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/__init__.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/client/__init__.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/client/abstract_tunnel.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/client/clear_nonce_task.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/client/kcp_tunnel_impl.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/client/n4_tunnel_manager.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/client/quic_tunnel_impl.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/client/tunnel_protocol.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/__init__.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/cert_utils.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/crypto/__init__.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/crypto/table.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/encrypt_utils.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/kcp.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/logger_factory.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/n4_protocol.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/n4_punch.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/nat_serialization.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/nat_serialization_v1.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/nat_serialization_v2.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/register_append_data.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/speed_limit.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket/__init__.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket/_abnf.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket/_app.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket/_cookiejar.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket/_core.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket/_dispatcher.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket/_exceptions.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket/_handshake.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket/_http.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket/_logging.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket/_socket.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket/_ssl_compat.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket/_url.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket/_utils.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket/_wsdump.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket/tests/__init__.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket/tests/echo-server.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket/tests/test_abnf.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket/tests/test_app.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket/tests/test_cookiejar.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket/tests/test_dispatcher.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket/tests/test_handshake_large_response.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket/tests/test_http.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket/tests/test_large_payloads.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket/tests/test_logging_helpers.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket/tests/test_reconnect_bad_fd.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket/tests/test_socket.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket/tests/test_socket_bugs.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket/tests/test_ssl_compat.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket/tests/test_ssl_edge_cases.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket/tests/test_url.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket/tests/test_utils.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket/tests/test_websocket.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket2/__init__.py +0 -0
- {proxynt-2.0.57/build/lib/proxynt/build/lib/proxynt → proxynt-2.0.58}/common/websocket2/compliance/autobahn-test-report-Feb-03-2021.html +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket2/setup.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket2/websocket/__init__.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket2/websocket/_abnf.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket2/websocket/_app.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket2/websocket/_cookiejar.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket2/websocket/_core.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket2/websocket/_dispatcher.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket2/websocket/_exceptions.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket2/websocket/_handshake.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket2/websocket/_http.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket2/websocket/_logging.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket2/websocket/_socket.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket2/websocket/_ssl_compat.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket2/websocket/_url.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket2/websocket/_utils.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket2/websocket/_wsdump.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket2/websocket/tests/__init__.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket2/websocket/tests/echo-server.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket2/websocket/tests/test_abnf.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket2/websocket/tests/test_app.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket2/websocket/tests/test_cookiejar.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket2/websocket/tests/test_dispatcher.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket2/websocket/tests/test_handshake_large_response.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket2/websocket/tests/test_http.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket2/websocket/tests/test_large_payloads.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket2/websocket/tests/test_logging_helpers.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket2/websocket/tests/test_reconnect_bad_fd.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket2/websocket/tests/test_socket.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket2/websocket/tests/test_socket_bugs.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket2/websocket/tests/test_ssl_compat.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket2/websocket/tests/test_ssl_edge_cases.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket2/websocket/tests/test_url.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket2/websocket/tests/test_utils.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/common/websocket2/websocket/tests/test_websocket.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/constant/__init__.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/constant/message_type_constnat.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/context/__init__.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/context/context_utils.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/entity/__init__.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/entity/client_config_entity.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/entity/message/__init__.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/entity/message/message_entity.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/entity/message/push_config_entity.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/entity/message/tcp_over_websocket_message.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/entity/server_config_entity.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/exceptions/__init__.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/exceptions/duplicated_name.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/exceptions/invalid_password.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/exceptions/replay_error.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/exceptions/signature_error.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/p2ptest/__init__.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/p2ptest/client.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/p2ptest/n4.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/proxynt.egg-info/dependency_links.txt +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/proxynt.egg-info/entry_points.txt +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/proxynt.egg-info/requires.txt +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/proxynt.egg-info/top_level.txt +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/run_server.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/server/__init__.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/server/admin_http_handler.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/server/n4.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/server/n4_signal_service.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/server/task/__init__.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/server/task/check_cookie_task.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/server/task/clear_nonce_task.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/server/task/heart_beat_task.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/server/tcp_forward_client.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/server/template/__init__.py +0 -0
- {proxynt-2.0.57/build/lib/proxynt/build/lib/proxynt → proxynt-2.0.58}/server/template/base.html +0 -0
- {proxynt-2.0.57/build/lib/proxynt/build/lib/proxynt → proxynt-2.0.58}/server/template/css/fonts/element-icons.woff +0 -0
- {proxynt-2.0.57/build/lib/proxynt/build/lib/proxynt → proxynt-2.0.58}/server/template/css/index.css +0 -0
- {proxynt-2.0.57/build/lib/proxynt/build/lib/proxynt → proxynt-2.0.58}/server/template/ele_index.html +0 -0
- {proxynt-2.0.57/build/lib/proxynt/build/lib/proxynt → proxynt-2.0.58}/server/template/js/axios.min.js +0 -0
- {proxynt-2.0.57/build/lib/proxynt/build/lib/proxynt → proxynt-2.0.58}/server/template/js/index.js +0 -0
- {proxynt-2.0.57/build/lib/proxynt/build/lib/proxynt → proxynt-2.0.58}/server/template/js/vue.min.js +0 -0
- {proxynt-2.0.57/build/lib/proxynt/build/lib/proxynt → proxynt-2.0.58}/server/template/login.html +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/server/udp_forward_client.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/server/websocket_handler.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/setup.cfg +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/setup.py +0 -0
- {proxynt-2.0.57 → proxynt-2.0.58}/test_exchange.py +0 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import time
|
|
3
|
+
import traceback
|
|
4
|
+
|
|
5
|
+
from common.logger_factory import LoggerFactory
|
|
6
|
+
from common.nat_serialization import NatSerialization
|
|
7
|
+
from constant.message_type_constnat import MessageTypeConstant
|
|
8
|
+
from constant.system_constant import SystemConstant
|
|
9
|
+
from context.context_utils import ContextUtils
|
|
10
|
+
from entity.message.message_entity import MessageEntity
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class HeatBeatTask:
|
|
14
|
+
def __init__(self, ws_sender, sleep_break: int, protocol_version: int):
|
|
15
|
+
self.ws_sender = ws_sender
|
|
16
|
+
self.is_running = False
|
|
17
|
+
self.recv_heart_beat_time: float = time.time()
|
|
18
|
+
self.sleep_break = sleep_break
|
|
19
|
+
self.protocol_version = protocol_version
|
|
20
|
+
self._periodic_callback = None
|
|
21
|
+
|
|
22
|
+
def set_recv_heart_beat_time(self, d: float):
|
|
23
|
+
self.recv_heart_beat_time = d
|
|
24
|
+
|
|
25
|
+
def start(self, io=None):
|
|
26
|
+
"""Start heartbeat PeriodicCallback on the IOLoop. No thread needed."""
|
|
27
|
+
from tornado.ioloop import PeriodicCallback, IOLoop
|
|
28
|
+
target_io = io or IOLoop.current()
|
|
29
|
+
# PeriodicCallback interval is in milliseconds
|
|
30
|
+
self._periodic_callback = PeriodicCallback(self._tick, self.sleep_break * 1000)
|
|
31
|
+
self._periodic_callback.start()
|
|
32
|
+
|
|
33
|
+
def stop(self):
|
|
34
|
+
if self._periodic_callback is not None:
|
|
35
|
+
self._periodic_callback.stop()
|
|
36
|
+
self._periodic_callback = None
|
|
37
|
+
|
|
38
|
+
def _tick(self):
|
|
39
|
+
"""Called by PeriodicCallback on the IOLoop."""
|
|
40
|
+
if not self.is_running:
|
|
41
|
+
return
|
|
42
|
+
if LoggerFactory.get_logger().isEnabledFor(logging.DEBUG):
|
|
43
|
+
LoggerFactory.get_logger().debug('run send heartbeat')
|
|
44
|
+
try:
|
|
45
|
+
self.send_heart_beat()
|
|
46
|
+
except Exception:
|
|
47
|
+
LoggerFactory.get_logger().error(traceback.format_exc())
|
|
48
|
+
try:
|
|
49
|
+
self.check_recv_heart_beat_time()
|
|
50
|
+
except Exception:
|
|
51
|
+
LoggerFactory.get_logger().error(traceback.format_exc())
|
|
52
|
+
|
|
53
|
+
def send_heart_beat(self):
|
|
54
|
+
if not self.is_running:
|
|
55
|
+
return
|
|
56
|
+
ping_message: MessageEntity = {
|
|
57
|
+
'type_': MessageTypeConstant.PING,
|
|
58
|
+
'data': None
|
|
59
|
+
}
|
|
60
|
+
self.ws_sender.send(
|
|
61
|
+
NatSerialization.dumps(ping_message, ContextUtils.get_password(), False, self.protocol_version)
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
def check_recv_heart_beat_time(self):
|
|
65
|
+
"""Close connection on heartbeat timeout."""
|
|
66
|
+
if not self.is_running:
|
|
67
|
+
return
|
|
68
|
+
elapsed = time.time() - self.recv_heart_beat_time
|
|
69
|
+
if elapsed > SystemConstant.MAX_HEART_BEAT_SECONDS:
|
|
70
|
+
LoggerFactory.get_logger().info(
|
|
71
|
+
'Heartbeat receive timeout %.1f s, closing connection' % elapsed
|
|
72
|
+
)
|
|
73
|
+
# Closing the Tornado ws triggers on_message(None) → on_close
|
|
74
|
+
self.ws_sender.close()
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import os
|
|
3
|
-
import queue
|
|
4
3
|
import socket
|
|
5
4
|
import threading
|
|
6
5
|
import time
|
|
@@ -8,7 +7,6 @@ import traceback
|
|
|
8
7
|
from threading import Lock
|
|
9
8
|
from typing import Dict, List
|
|
10
9
|
|
|
11
|
-
from common import websocket
|
|
12
10
|
from common.logger_factory import LoggerFactory
|
|
13
11
|
from common.nat_serialization import NatSerialization
|
|
14
12
|
from common.pool import SelectPool
|
|
@@ -19,169 +17,62 @@ from context.context_utils import ContextUtils
|
|
|
19
17
|
from entity.message.message_entity import MessageEntity
|
|
20
18
|
|
|
21
19
|
|
|
22
|
-
class MessageSender:
|
|
23
|
-
"""
|
|
24
|
-
Dual priority queue message sender
|
|
25
|
-
- High priority queue: for heartbeat and control messages (latency < 10ms)
|
|
26
|
-
- Normal queue: for business data
|
|
27
|
-
"""
|
|
28
|
-
|
|
29
|
-
def __init__(self, ws):
|
|
30
|
-
# Dual priority queues
|
|
31
|
-
self.high_priority_queue = queue.Queue(maxsize=64) # High priority: control messages
|
|
32
|
-
self.normal_queue = queue.Queue(maxsize=1024) # Normal priority: business data
|
|
33
|
-
self.ws = ws
|
|
34
|
-
self.running = False
|
|
35
|
-
self.sender_thread = threading.Thread(target=self.send_messages)
|
|
36
|
-
|
|
37
|
-
def send_messages(self):
|
|
38
|
-
"""Priority send loop"""
|
|
39
|
-
while self.running or not self.normal_queue.empty() or not self.high_priority_queue.empty():
|
|
40
|
-
try:
|
|
41
|
-
send_bytes = None
|
|
42
|
-
|
|
43
|
-
# 1. Process high priority queue first (non-blocking)
|
|
44
|
-
try:
|
|
45
|
-
send_bytes = self.high_priority_queue.get_nowait()
|
|
46
|
-
except queue.Empty:
|
|
47
|
-
pass
|
|
48
|
-
|
|
49
|
-
# 2. If high priority queue is empty, get from normal queue (blocking)
|
|
50
|
-
if send_bytes is None:
|
|
51
|
-
try:
|
|
52
|
-
send_bytes = self.normal_queue.get(timeout=1)
|
|
53
|
-
except queue.Empty:
|
|
54
|
-
continue
|
|
55
|
-
|
|
56
|
-
# 3. Send message
|
|
57
|
-
self.ws.send(send_bytes, opcode=websocket.ABNF.OPCODE_BINARY)
|
|
58
|
-
|
|
59
|
-
# Mark task as done
|
|
60
|
-
try:
|
|
61
|
-
self.high_priority_queue.task_done()
|
|
62
|
-
except ValueError:
|
|
63
|
-
self.normal_queue.task_done()
|
|
64
|
-
|
|
65
|
-
except Exception as e:
|
|
66
|
-
LoggerFactory.get_logger().error(f"Failed to send message: {e}")
|
|
67
|
-
|
|
68
|
-
def enqueue_message(self, message, high_priority=False):
|
|
69
|
-
"""
|
|
70
|
-
Add message to queue
|
|
71
|
-
:param message: Message content
|
|
72
|
-
:param high_priority: Whether high priority (heartbeat, control messages)
|
|
73
|
-
"""
|
|
74
|
-
if self.running:
|
|
75
|
-
if high_priority:
|
|
76
|
-
try:
|
|
77
|
-
self.high_priority_queue.put_nowait(message)
|
|
78
|
-
except queue.Full:
|
|
79
|
-
LoggerFactory.get_logger().warning("High priority queue full")
|
|
80
|
-
else:
|
|
81
|
-
try:
|
|
82
|
-
self.normal_queue.put(message, timeout=5)
|
|
83
|
-
except queue.Full:
|
|
84
|
-
LoggerFactory.get_logger().warning("Normal queue full")
|
|
85
|
-
else:
|
|
86
|
-
LoggerFactory.get_logger().warning("WebSocket is not running. Cannot enqueue message.")
|
|
87
|
-
|
|
88
|
-
def start(self):
|
|
89
|
-
self.running = True
|
|
90
|
-
self.sender_thread.start()
|
|
91
|
-
|
|
92
|
-
def stop(self):
|
|
93
|
-
"""Safe stop: send high priority first, then normal queue"""
|
|
94
|
-
def safe_stop():
|
|
95
|
-
# 1. Send high priority queue first
|
|
96
|
-
while not self.high_priority_queue.empty():
|
|
97
|
-
try:
|
|
98
|
-
message = self.high_priority_queue.get_nowait()
|
|
99
|
-
self.ws.send(message, opcode=websocket.ABNF.OPCODE_BINARY)
|
|
100
|
-
self.high_priority_queue.task_done()
|
|
101
|
-
except queue.Empty:
|
|
102
|
-
break
|
|
103
|
-
except Exception as e:
|
|
104
|
-
LoggerFactory.get_logger().error(f"Failed to send high priority message during stop: {e}")
|
|
105
|
-
|
|
106
|
-
# 2. Then send normal queue
|
|
107
|
-
while not self.normal_queue.empty():
|
|
108
|
-
try:
|
|
109
|
-
message = self.normal_queue.get_nowait()
|
|
110
|
-
self.ws.send(message, opcode=websocket.ABNF.OPCODE_BINARY)
|
|
111
|
-
self.normal_queue.task_done()
|
|
112
|
-
except queue.Empty:
|
|
113
|
-
break
|
|
114
|
-
except Exception as e:
|
|
115
|
-
LoggerFactory.get_logger().error(f"Failed to send normal message during stop: {e}")
|
|
116
|
-
|
|
117
|
-
self.running = False
|
|
118
|
-
safe_stop()
|
|
119
|
-
|
|
120
|
-
|
|
121
20
|
class PrivateSocketConnection:
|
|
122
21
|
"""Client connecting to internal network port"""
|
|
123
22
|
|
|
124
|
-
def __init__(self, uid: bytes, s: socket.socket, name: str
|
|
23
|
+
def __init__(self, uid: bytes, s: socket.socket, name: str):
|
|
125
24
|
self.uid: bytes = uid
|
|
126
25
|
self.socket: socket.socket = s
|
|
127
26
|
self.name: str = name
|
|
128
|
-
self.sender = MessageSender(ws)
|
|
129
|
-
self.sender.start()
|
|
130
27
|
|
|
131
28
|
|
|
132
29
|
class TcpForwardClient:
|
|
133
|
-
def __init__(self,
|
|
30
|
+
def __init__(self, ws_sender, compress_support: bool, protocol_version: int):
|
|
134
31
|
self.uid_to_socket_connection: Dict[bytes, PrivateSocketConnection] = dict()
|
|
135
32
|
self.socket_to_socket_connection: Dict[socket.socket, PrivateSocketConnection] = dict()
|
|
136
33
|
self.compress_support: bool = compress_support
|
|
137
34
|
self.protocol_version: int = protocol_version
|
|
138
|
-
self.ws =
|
|
35
|
+
self.ws = ws_sender # WsSender — thread-safe, send via IOLoop.add_callback
|
|
139
36
|
self.lock = Lock()
|
|
140
37
|
self.socket_event_loop = SelectPool()
|
|
141
38
|
|
|
142
39
|
# C2C client-to-client forward state
|
|
143
|
-
self.c2c_rules: List[dict] = []
|
|
144
|
-
self.c2c_listeners: Dict[str, socket.socket] = {}
|
|
145
|
-
self.c2c_uid_to_rule: Dict[bytes, str] = {}
|
|
40
|
+
self.c2c_rules: List[dict] = []
|
|
41
|
+
self.c2c_listeners: Dict[str, socket.socket] = {}
|
|
42
|
+
self.c2c_uid_to_rule: Dict[bytes, str] = {}
|
|
146
43
|
|
|
147
44
|
# N4 Tunnel Manager for P2P data transfer
|
|
148
45
|
self.tunnel_manager = None
|
|
149
46
|
|
|
150
47
|
# Pending data buffer: uid -> list of (data, timestamp)
|
|
151
|
-
# Used to buffer P2P data that arrives before connection is established
|
|
152
48
|
self.pending_data: Dict[bytes, list] = {}
|
|
153
49
|
self.pending_data_lock = Lock()
|
|
154
|
-
self.pending_data_timeout = 10
|
|
50
|
+
self.pending_data_timeout = 10
|
|
155
51
|
|
|
156
52
|
def set_running(self, running: bool):
|
|
157
53
|
self.socket_event_loop.is_running = running
|
|
158
54
|
|
|
159
55
|
def update_websocket(self, ws):
|
|
160
|
-
"""
|
|
161
|
-
|
|
162
|
-
LoggerFactory.get_logger().info('TCP forward client websocket reference updated')
|
|
56
|
+
"""No-op: ws_sender reference is stable; connection is updated inside WsSender."""
|
|
57
|
+
pass
|
|
163
58
|
|
|
164
59
|
def start_forward(self):
|
|
165
60
|
self.socket_event_loop.run()
|
|
166
61
|
|
|
167
62
|
def setup_c2c_tcp_listeners(self, c2c_rules: List[dict]):
|
|
168
|
-
|
|
169
|
-
Setup C2C TCP listeners
|
|
170
|
-
Create local listeners for each C2C rule to accept connections from local applications
|
|
171
|
-
"""
|
|
172
|
-
# Clean old listeners first to avoid port conflicts
|
|
63
|
+
# Clean old listeners first
|
|
173
64
|
for rule_name, listener in list(self.c2c_listeners.items()):
|
|
174
65
|
try:
|
|
175
66
|
listener.close()
|
|
176
|
-
LoggerFactory.get_logger().info(
|
|
67
|
+
LoggerFactory.get_logger().info('Cleaned old C2C TCP listener: %s' % rule_name)
|
|
177
68
|
except Exception as e:
|
|
178
|
-
LoggerFactory.get_logger().error(
|
|
69
|
+
LoggerFactory.get_logger().error('Failed to close old C2C TCP listener %s: %s' % (rule_name, e))
|
|
179
70
|
|
|
180
71
|
self.c2c_listeners.clear()
|
|
181
72
|
self.c2c_uid_to_rule.clear()
|
|
182
73
|
|
|
183
74
|
self.c2c_rules = c2c_rules
|
|
184
|
-
LoggerFactory.get_logger().info(
|
|
75
|
+
LoggerFactory.get_logger().info('Setting up %d C2C TCP listeners' % len(c2c_rules))
|
|
185
76
|
|
|
186
77
|
for rule in c2c_rules:
|
|
187
78
|
if rule['protocol'] != 'tcp':
|
|
@@ -192,16 +83,14 @@ class TcpForwardClient:
|
|
|
192
83
|
local_port = rule['local_port']
|
|
193
84
|
|
|
194
85
|
try:
|
|
195
|
-
# Create listener socket
|
|
196
86
|
listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
197
87
|
listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
198
88
|
listener.bind((local_ip, local_port))
|
|
199
89
|
listener.listen(128)
|
|
200
90
|
|
|
201
91
|
self.c2c_listeners[rule_name] = listener
|
|
202
|
-
LoggerFactory.get_logger().info(
|
|
92
|
+
LoggerFactory.get_logger().info('C2C TCP listener created: %s on %s:%d' % (rule_name, local_ip, local_port))
|
|
203
93
|
|
|
204
|
-
# Start separate thread to handle connection acceptance
|
|
205
94
|
accept_thread = threading.Thread(
|
|
206
95
|
target=self.handle_c2c_tcp_accept,
|
|
207
96
|
args=(listener, rule),
|
|
@@ -210,50 +99,38 @@ class TcpForwardClient:
|
|
|
210
99
|
accept_thread.start()
|
|
211
100
|
|
|
212
101
|
except Exception as e:
|
|
213
|
-
LoggerFactory.get_logger().error(
|
|
102
|
+
LoggerFactory.get_logger().error('Failed to create C2C TCP listener %s: %s' % (rule_name, e))
|
|
214
103
|
LoggerFactory.get_logger().error(traceback.format_exc())
|
|
215
104
|
|
|
216
105
|
def handle_c2c_tcp_accept(self, listener: socket.socket, rule: dict):
|
|
217
|
-
"""
|
|
218
|
-
Handle C2C TCP connection acceptance
|
|
219
|
-
Loop to accept connections from local applications, send CLIENT_TO_CLIENT_FORWARD request for each connection
|
|
220
|
-
"""
|
|
221
106
|
rule_name = rule['name']
|
|
222
107
|
target_client = rule['target_client']
|
|
223
108
|
protocol = rule['protocol']
|
|
224
109
|
speed_limit = rule.get('speed_limit', 0.0)
|
|
225
110
|
|
|
226
|
-
# Check if using direct mode (target_ip + target_port) or service mode (target_service)
|
|
227
111
|
use_direct_mode = 'target_ip' in rule and 'target_port' in rule
|
|
228
112
|
|
|
229
|
-
LoggerFactory.get_logger().info(
|
|
113
|
+
LoggerFactory.get_logger().info('C2C TCP accept thread started: %s' % rule_name)
|
|
230
114
|
|
|
231
115
|
while True:
|
|
232
116
|
try:
|
|
233
117
|
client_socket, client_addr = listener.accept()
|
|
234
|
-
LoggerFactory.get_logger().info(
|
|
118
|
+
LoggerFactory.get_logger().info('C2C TCP connection accepted: %s from %s' % (rule_name, client_addr))
|
|
235
119
|
|
|
236
|
-
# Enable TCP_NODELAY to reduce latency
|
|
237
120
|
client_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
|
238
121
|
|
|
239
|
-
# Generate unique UID
|
|
240
122
|
uid = os.urandom(4)
|
|
241
123
|
|
|
242
|
-
# Store UID → rule_name mapping
|
|
243
124
|
with self.lock:
|
|
244
125
|
self.c2c_uid_to_rule[uid] = rule_name
|
|
245
|
-
|
|
246
|
-
# Create connection object
|
|
247
|
-
connection = PrivateSocketConnection(uid, client_socket, rule_name, self.ws)
|
|
126
|
+
connection = PrivateSocketConnection(uid, client_socket, rule_name)
|
|
248
127
|
self.uid_to_socket_connection[uid] = connection
|
|
249
128
|
self.socket_to_socket_connection[client_socket] = connection
|
|
250
129
|
|
|
251
|
-
# Register UID to tunnel_manager for P2P routing
|
|
252
130
|
if self.tunnel_manager:
|
|
253
131
|
self.tunnel_manager.register_uid(uid, target_client)
|
|
254
|
-
LoggerFactory.get_logger().info(
|
|
132
|
+
LoggerFactory.get_logger().info('Registered UID %s to peer %s' % (uid.hex(), target_client))
|
|
255
133
|
|
|
256
|
-
# Send CLIENT_TO_CLIENT_FORWARD message to server
|
|
257
134
|
forward_data = {
|
|
258
135
|
'uid': uid,
|
|
259
136
|
'target_client': target_client,
|
|
@@ -261,7 +138,6 @@ class TcpForwardClient:
|
|
|
261
138
|
'protocol': protocol
|
|
262
139
|
}
|
|
263
140
|
|
|
264
|
-
# Add target_ip and target_port for direct mode, or target_service for service mode
|
|
265
141
|
if use_direct_mode:
|
|
266
142
|
forward_data['target_ip'] = rule['target_ip']
|
|
267
143
|
forward_data['target_port'] = rule['target_port']
|
|
@@ -273,24 +149,20 @@ class TcpForwardClient:
|
|
|
273
149
|
'data': forward_data
|
|
274
150
|
}
|
|
275
151
|
self.ws.send(
|
|
276
|
-
NatSerialization.dumps(forward_message, ContextUtils.get_password(), self.compress_support, self.protocol_version)
|
|
277
|
-
websocket.ABNF.OPCODE_BINARY
|
|
152
|
+
NatSerialization.dumps(forward_message, ContextUtils.get_password(), self.compress_support, self.protocol_version)
|
|
278
153
|
)
|
|
279
|
-
LoggerFactory.get_logger().info(
|
|
154
|
+
LoggerFactory.get_logger().info('C2C forward request sent: %s UID: %s' % (rule_name, uid.hex()))
|
|
280
155
|
|
|
281
|
-
# Register to event loop, start forwarding data
|
|
282
156
|
speed_limiter = SpeedLimiter(speed_limit) if speed_limit > 0 else None
|
|
283
157
|
self.socket_event_loop.register(client_socket, ResisterAppendData(self.handle_message, speed_limiter))
|
|
284
158
|
|
|
285
|
-
except OSError
|
|
286
|
-
|
|
287
|
-
LoggerFactory.get_logger().info(f'C2C TCP listener closed: {rule_name}')
|
|
159
|
+
except OSError:
|
|
160
|
+
LoggerFactory.get_logger().info('C2C TCP listener closed: %s' % rule_name)
|
|
288
161
|
break
|
|
289
162
|
except Exception as e:
|
|
290
|
-
LoggerFactory.get_logger().error(
|
|
163
|
+
LoggerFactory.get_logger().error('C2C TCP accept connection error %s: %s' % (rule_name, e))
|
|
291
164
|
LoggerFactory.get_logger().error(traceback.format_exc())
|
|
292
165
|
|
|
293
|
-
|
|
294
166
|
def handle_message(self, each: socket.socket, data: ResisterAppendData):
|
|
295
167
|
connection = self.socket_to_socket_connection.get(each)
|
|
296
168
|
if not connection:
|
|
@@ -301,22 +173,17 @@ class TcpForwardClient:
|
|
|
301
173
|
except OSError:
|
|
302
174
|
recv = b''
|
|
303
175
|
|
|
304
|
-
# --- 发送端限速:在发送前等待 ---
|
|
305
176
|
if data.speed_limiter and recv:
|
|
306
177
|
wait_time = data.speed_limiter.acquire(len(recv))
|
|
307
178
|
if wait_time > 0:
|
|
308
179
|
time.sleep(wait_time)
|
|
309
180
|
|
|
310
|
-
# --- 无缝切换逻辑 ---
|
|
311
181
|
if self.tunnel_manager:
|
|
312
|
-
# 尝试走 P2P 隧道
|
|
313
182
|
if self.tunnel_manager.send_data(connection.uid, recv):
|
|
314
|
-
|
|
315
|
-
if not recv: # 本地连接关闭,通知隧道
|
|
183
|
+
if not recv:
|
|
316
184
|
self.close_connection(each)
|
|
317
185
|
return
|
|
318
186
|
|
|
319
|
-
# --- 回退/初始逻辑:走 WebSocket ---
|
|
320
187
|
send_message: MessageEntity = {
|
|
321
188
|
'type_': MessageTypeConstant.WEBSOCKET_OVER_TCP,
|
|
322
189
|
'data': {
|
|
@@ -327,7 +194,7 @@ class TcpForwardClient:
|
|
|
327
194
|
}
|
|
328
195
|
}
|
|
329
196
|
|
|
330
|
-
|
|
197
|
+
self.ws.send(
|
|
331
198
|
NatSerialization.dumps(send_message, ContextUtils.get_password(), self.compress_support, self.protocol_version)
|
|
332
199
|
)
|
|
333
200
|
|
|
@@ -342,28 +209,25 @@ class TcpForwardClient:
|
|
|
342
209
|
return True
|
|
343
210
|
connection = None
|
|
344
211
|
with self.lock:
|
|
345
|
-
if uid in self.uid_to_socket_connection:
|
|
212
|
+
if uid in self.uid_to_socket_connection:
|
|
346
213
|
return True
|
|
347
214
|
try:
|
|
348
215
|
if LoggerFactory.get_logger().isEnabledFor(logging.DEBUG):
|
|
349
|
-
LoggerFactory.get_logger().debug(
|
|
216
|
+
LoggerFactory.get_logger().debug('create socket %s, %s' % (name, uid))
|
|
350
217
|
|
|
351
|
-
# Validate ip_port before splitting
|
|
352
218
|
if not ip_port or ':' not in ip_port:
|
|
353
|
-
LoggerFactory.get_logger().error(
|
|
219
|
+
LoggerFactory.get_logger().error('Invalid ip_port: %r, name: %s, uid: %s' % (ip_port, name, uid.hex()))
|
|
354
220
|
return False
|
|
355
221
|
|
|
356
222
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
357
223
|
s.settimeout(5)
|
|
358
|
-
# Enable TCP_NODELAY to reduce latency (disable Nagle algorithm)
|
|
359
224
|
s.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
|
360
|
-
connection = PrivateSocketConnection(uid, s, name
|
|
225
|
+
connection = PrivateSocketConnection(uid, s, name)
|
|
361
226
|
self.socket_to_socket_connection[s] = connection
|
|
362
227
|
ip, port = ip_port.split(':')
|
|
363
228
|
try:
|
|
364
229
|
s.connect((ip, int(port)))
|
|
365
230
|
|
|
366
|
-
# Optimistic send mode: send confirmation message immediately after connection success
|
|
367
231
|
confirm_message: MessageEntity = {
|
|
368
232
|
'type_': MessageTypeConstant.CONNECT_CONFIRMED,
|
|
369
233
|
'data': {
|
|
@@ -373,14 +237,15 @@ class TcpForwardClient:
|
|
|
373
237
|
'ip_port': ip_port
|
|
374
238
|
}
|
|
375
239
|
}
|
|
376
|
-
self.ws.send(
|
|
240
|
+
self.ws.send(
|
|
241
|
+
NatSerialization.dumps(confirm_message, ContextUtils.get_password(), self.compress_support, self.protocol_version)
|
|
242
|
+
)
|
|
377
243
|
if LoggerFactory.get_logger().isEnabledFor(logging.DEBUG):
|
|
378
|
-
LoggerFactory.get_logger().debug(
|
|
244
|
+
LoggerFactory.get_logger().debug('Connection confirmation message sent uid: %s' % uid)
|
|
379
245
|
|
|
380
246
|
except OSError as e:
|
|
381
|
-
LoggerFactory.get_logger().info(
|
|
247
|
+
LoggerFactory.get_logger().info('connection error, %s' % e)
|
|
382
248
|
|
|
383
|
-
# Optimistic send mode: send failure message after connection failure
|
|
384
249
|
fail_message: MessageEntity = {
|
|
385
250
|
'type_': MessageTypeConstant.CONNECT_FAILED,
|
|
386
251
|
'data': {
|
|
@@ -391,9 +256,11 @@ class TcpForwardClient:
|
|
|
391
256
|
}
|
|
392
257
|
}
|
|
393
258
|
try:
|
|
394
|
-
self.ws.send(
|
|
259
|
+
self.ws.send(
|
|
260
|
+
NatSerialization.dumps(fail_message, ContextUtils.get_password(), self.compress_support, self.protocol_version)
|
|
261
|
+
)
|
|
395
262
|
except Exception as send_err:
|
|
396
|
-
LoggerFactory.get_logger().error(
|
|
263
|
+
LoggerFactory.get_logger().error('Failed to send connection failure message: %s' % send_err)
|
|
397
264
|
|
|
398
265
|
self.close_connection(s)
|
|
399
266
|
self.close_remote_socket(connection)
|
|
@@ -401,12 +268,11 @@ class TcpForwardClient:
|
|
|
401
268
|
|
|
402
269
|
self.uid_to_socket_connection[uid] = connection
|
|
403
270
|
if LoggerFactory.get_logger().isEnabledFor(logging.DEBUG):
|
|
404
|
-
LoggerFactory.get_logger().debug(
|
|
271
|
+
LoggerFactory.get_logger().debug('register socket %s, %s' % (name, uid))
|
|
405
272
|
self.socket_event_loop.register(s, ResisterAppendData(self.handle_message, speed_limiter))
|
|
406
273
|
if LoggerFactory.get_logger().isEnabledFor(logging.DEBUG):
|
|
407
|
-
LoggerFactory.get_logger().debug(
|
|
274
|
+
LoggerFactory.get_logger().debug('register socket success %s, %s' % (name, uid))
|
|
408
275
|
|
|
409
|
-
# Process any pending data that arrived before connection was established
|
|
410
276
|
self._flush_pending_data(uid, s)
|
|
411
277
|
|
|
412
278
|
return True
|
|
@@ -417,35 +283,33 @@ class TcpForwardClient:
|
|
|
417
283
|
return False
|
|
418
284
|
|
|
419
285
|
def close_connection(self, socket_client: socket.socket):
|
|
420
|
-
LoggerFactory.get_logger().info(
|
|
286
|
+
LoggerFactory.get_logger().info('Closing socket %s' % socket_client)
|
|
421
287
|
if socket_client in self.socket_to_socket_connection:
|
|
422
288
|
connection: PrivateSocketConnection = self.socket_to_socket_connection.pop(socket_client)
|
|
423
289
|
self.socket_event_loop.unregister(socket_client)
|
|
424
290
|
try:
|
|
425
291
|
socket_client.shutdown(socket.SHUT_RDWR)
|
|
426
292
|
except OSError as e:
|
|
427
|
-
LoggerFactory.get_logger().warn(
|
|
293
|
+
LoggerFactory.get_logger().warn('Shutdown OS error %s' % e)
|
|
428
294
|
socket_client.close()
|
|
429
|
-
|
|
430
|
-
LoggerFactory.get_logger().info(f'Socket closed successfully {socket_client}')
|
|
295
|
+
LoggerFactory.get_logger().info('Socket closed successfully %s' % socket_client)
|
|
431
296
|
if connection.uid in self.uid_to_socket_connection:
|
|
432
297
|
self.uid_to_socket_connection.pop(connection.uid)
|
|
433
298
|
self.close_remote_socket(connection)
|
|
434
299
|
|
|
435
300
|
def close(self):
|
|
436
|
-
LoggerFactory.get_logger().info(
|
|
301
|
+
LoggerFactory.get_logger().info('Starting to close %s' % self.c2c_listeners)
|
|
437
302
|
with self.lock:
|
|
438
|
-
# Close all C2C listeners
|
|
439
303
|
for rule_name, listener in self.c2c_listeners.items():
|
|
440
304
|
try:
|
|
441
305
|
try:
|
|
442
306
|
listener.shutdown(socket.SHUT_RDWR)
|
|
443
307
|
except OSError as e:
|
|
444
|
-
LoggerFactory.get_logger().warn(
|
|
308
|
+
LoggerFactory.get_logger().warn('Shutdown OS error %s' % e)
|
|
445
309
|
listener.close()
|
|
446
|
-
LoggerFactory.get_logger().info(
|
|
310
|
+
LoggerFactory.get_logger().info('C2C TCP listener closed: %s' % rule_name)
|
|
447
311
|
except Exception as e:
|
|
448
|
-
LoggerFactory.get_logger().error(
|
|
312
|
+
LoggerFactory.get_logger().error('Failed to close C2C TCP listener %s: %s' % (rule_name, e))
|
|
449
313
|
|
|
450
314
|
self.c2c_listeners.clear()
|
|
451
315
|
self.c2c_rules.clear()
|
|
@@ -462,21 +326,16 @@ class TcpForwardClient:
|
|
|
462
326
|
try:
|
|
463
327
|
s.shutdown(socket.SHUT_RDWR)
|
|
464
328
|
except OSError as e:
|
|
465
|
-
LoggerFactory.get_logger().warn(
|
|
329
|
+
LoggerFactory.get_logger().warn('Shutdown OS error %s' % e)
|
|
466
330
|
s.close()
|
|
467
331
|
except Exception:
|
|
468
332
|
LoggerFactory.get_logger().error(traceback.format_exc())
|
|
469
|
-
c.sender.stop()
|
|
470
333
|
self.uid_to_socket_connection.clear()
|
|
471
334
|
self.socket_to_socket_connection.clear()
|
|
472
335
|
self.set_running(False)
|
|
473
336
|
self.socket_event_loop.clear()
|
|
474
337
|
|
|
475
338
|
def close_remote_socket(self, connection: PrivateSocketConnection):
|
|
476
|
-
# if name is None:
|
|
477
|
-
# connection = self.uid_to_socket_connection.get(uid)
|
|
478
|
-
# if not connection:
|
|
479
|
-
# return
|
|
480
339
|
name = connection.name
|
|
481
340
|
send_message: MessageEntity = {
|
|
482
341
|
'type_': MessageTypeConstant.WEBSOCKET_OVER_TCP,
|
|
@@ -488,43 +347,33 @@ class TcpForwardClient:
|
|
|
488
347
|
}
|
|
489
348
|
}
|
|
490
349
|
start_time = time.time()
|
|
491
|
-
self.ws.send(
|
|
492
|
-
|
|
350
|
+
self.ws.send(
|
|
351
|
+
NatSerialization.dumps(send_message, ContextUtils.get_password(), self.compress_support, self.protocol_version)
|
|
352
|
+
)
|
|
353
|
+
LoggerFactory.get_logger().debug('Send to websocket cost time %s' % (time.time() - start_time))
|
|
493
354
|
|
|
494
355
|
def send_by_uid(self, uid: bytes, msg: bytes):
|
|
495
356
|
connection = self.uid_to_socket_connection.get(uid)
|
|
496
357
|
if not connection:
|
|
497
|
-
|
|
498
|
-
# This handles the race condition where P2P data arrives before
|
|
499
|
-
# the REQUEST_TO_CONNECT message establishes the connection
|
|
500
|
-
if msg: # Only buffer non-empty data
|
|
358
|
+
if msg:
|
|
501
359
|
with self.pending_data_lock:
|
|
502
360
|
if uid not in self.pending_data:
|
|
503
361
|
self.pending_data[uid] = []
|
|
504
362
|
self.pending_data[uid].append((msg, time.time()))
|
|
505
363
|
LoggerFactory.get_logger().debug(
|
|
506
|
-
|
|
364
|
+
'Buffered %d bytes for UID %s (connection pending)' % (len(msg), uid.hex())
|
|
507
365
|
)
|
|
508
366
|
return
|
|
509
367
|
try:
|
|
510
|
-
if LoggerFactory.get_logger().isEnabledFor(logging.DEBUG):
|
|
511
|
-
LoggerFactory.get_logger().debug(f'Starting to send to socket uid: {uid}, length: {len(msg)}')
|
|
512
368
|
s = connection.socket
|
|
513
369
|
s.sendall(msg)
|
|
514
|
-
if LoggerFactory.get_logger().isEnabledFor(logging.DEBUG):
|
|
515
|
-
LoggerFactory.get_logger().debug(f'Finished sending to socket uid {uid}, length: {len(msg)}')
|
|
516
370
|
if not msg:
|
|
517
371
|
self.close_connection(s)
|
|
518
372
|
except Exception:
|
|
519
373
|
LoggerFactory.get_logger().error(traceback.format_exc())
|
|
520
|
-
# After error, send empty message, server will close connection
|
|
521
374
|
self.close_remote_socket(connection)
|
|
522
375
|
|
|
523
376
|
def _flush_pending_data(self, uid: bytes, sock: socket.socket):
|
|
524
|
-
"""
|
|
525
|
-
Flush any pending data that was buffered before connection was established.
|
|
526
|
-
This solves the race condition where P2P data arrives before REQUEST_TO_CONNECT.
|
|
527
|
-
"""
|
|
528
377
|
pending_items = None
|
|
529
378
|
with self.pending_data_lock:
|
|
530
379
|
if uid in self.pending_data:
|
|
@@ -538,20 +387,18 @@ class TcpForwardClient:
|
|
|
538
387
|
expired_count = 0
|
|
539
388
|
|
|
540
389
|
for data, timestamp in pending_items:
|
|
541
|
-
# Skip expired data
|
|
542
390
|
if now - timestamp > self.pending_data_timeout:
|
|
543
391
|
expired_count += 1
|
|
544
392
|
continue
|
|
545
|
-
|
|
546
393
|
try:
|
|
547
394
|
sock.sendall(data)
|
|
548
395
|
total_sent += len(data)
|
|
549
396
|
except Exception as e:
|
|
550
|
-
LoggerFactory.get_logger().error(
|
|
397
|
+
LoggerFactory.get_logger().error('Failed to flush pending data for UID %s: %s' % (uid.hex(), e))
|
|
551
398
|
break
|
|
552
399
|
|
|
553
400
|
if total_sent > 0 or expired_count > 0:
|
|
554
401
|
LoggerFactory.get_logger().info(
|
|
555
|
-
|
|
556
|
-
|
|
402
|
+
'Flushed pending data for UID %s: %d bytes sent, %d expired packets dropped'
|
|
403
|
+
% (uid.hex(), total_sent, expired_count)
|
|
557
404
|
)
|