proxynt 2.0.24__tar.gz → 2.0.26__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.24 → proxynt-2.0.26}/PKG-INFO +1 -1
- proxynt-2.0.26/client/data_connection_manager.py +250 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/client/tcp_forward_client.py +30 -12
- {proxynt-2.0.24 → proxynt-2.0.26}/client/udp_forward_client.py +37 -14
- proxynt-2.0.26/common/speed_limit.py +89 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/constant/message_type_constnat.py +4 -1
- {proxynt-2.0.24 → proxynt-2.0.26}/constant/system_constant.py +1 -1
- {proxynt-2.0.24 → proxynt-2.0.26}/proxynt.egg-info/PKG-INFO +1 -1
- {proxynt-2.0.24 → proxynt-2.0.26}/proxynt.egg-info/SOURCES.txt +2 -0
- proxynt-2.0.26/proxynt.egg-info/requires.txt +12 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/run_client.py +102 -9
- {proxynt-2.0.24 → proxynt-2.0.26}/run_server.py +18 -8
- proxynt-2.0.26/server/session_manager.py +288 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/server/tcp_forward_client.py +16 -8
- {proxynt-2.0.24 → proxynt-2.0.26}/server/udp_forward_client.py +15 -11
- {proxynt-2.0.24 → proxynt-2.0.26}/server/websocket_handler.py +193 -33
- {proxynt-2.0.24 → proxynt-2.0.26}/setup.py +2 -2
- proxynt-2.0.24/common/speed_limit.py +0 -33
- proxynt-2.0.24/proxynt.egg-info/requires.txt +0 -12
- {proxynt-2.0.24 → proxynt-2.0.26}/LICENSE +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/MANIFEST.in +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/__init__.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/client/__init__.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/client/abstract_tunnel.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/client/clear_nonce_task.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/client/heart_beat_task.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/client/kcp_tunnel_impl.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/client/n4_tunnel_manager.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/client/quic_tunnel_impl.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/client/tunnel_protocol.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/common/__init__.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/common/cert_utils.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/common/crypto/__init__.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/common/crypto/table.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/common/encrypt_utils.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/common/kcp.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/common/logger_factory.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/common/n4_protocol.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/common/n4_punch.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/common/nat_serialization.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/common/pool.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/common/register_append_data.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/common/websocket/__init__.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/common/websocket/_abnf.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/common/websocket/_app.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/common/websocket/_cookiejar.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/common/websocket/_core.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/common/websocket/_exceptions.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/common/websocket/_handshake.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/common/websocket/_http.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/common/websocket/_logging.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/common/websocket/_socket.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/common/websocket/_ssl_compat.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/common/websocket/_url.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/common/websocket/_utils.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/common/websocket/_wsdump.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/common/websocket/tests/__init__.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/common/websocket/tests/echo-server.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/common/websocket/tests/test_abnf.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/common/websocket/tests/test_app.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/common/websocket/tests/test_cookiejar.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/common/websocket/tests/test_http.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/common/websocket/tests/test_url.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/common/websocket/tests/test_websocket.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/constant/__init__.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/context/__init__.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/context/context_utils.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/entity/__init__.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/entity/client_config_entity.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/entity/message/__init__.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/entity/message/message_entity.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/entity/message/push_config_entity.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/entity/message/tcp_over_websocket_message.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/entity/server_config_entity.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/exceptions/__init__.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/exceptions/duplicated_name.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/exceptions/invalid_password.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/exceptions/replay_error.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/exceptions/signature_error.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/p2ptest/__init__.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/p2ptest/client.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/p2ptest/n4.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/proxynt.egg-info/dependency_links.txt +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/proxynt.egg-info/entry_points.txt +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/proxynt.egg-info/top_level.txt +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/server/__init__.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/server/admin_http_handler.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/server/n4.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/server/n4_signal_service.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/server/task/__init__.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/server/task/check_cookie_task.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/server/task/clear_nonce_task.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/server/task/heart_beat_task.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/server/template/__init__.py +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/server/template/base.html +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/server/template/css/fonts/element-icons.woff +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/server/template/css/index.css +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/server/template/ele_index.html +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/server/template/js/axios.min.js +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/server/template/js/index.js +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/server/template/js/vue.min.js +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/server/template/login.html +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/setup.cfg +0 -0
- {proxynt-2.0.24 → proxynt-2.0.26}/test_exchange.py +0 -0
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
"""
|
|
2
|
+
客户端数据连接管理器
|
|
3
|
+
|
|
4
|
+
管理多个 WebSocket 数据连接:
|
|
5
|
+
- 建立 N 个数据连接并加入 Session
|
|
6
|
+
- 根据 UID hash 选择数据连接发送数据
|
|
7
|
+
- 处理数据连接断开重连
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import threading
|
|
12
|
+
import time
|
|
13
|
+
import traceback
|
|
14
|
+
from typing import List, Optional, Callable
|
|
15
|
+
from urllib.parse import urlparse
|
|
16
|
+
|
|
17
|
+
from common import websocket
|
|
18
|
+
from common.logger_factory import LoggerFactory
|
|
19
|
+
from common.nat_serialization import NatSerialization
|
|
20
|
+
from constant.message_type_constnat import MessageTypeConstant
|
|
21
|
+
from context.context_utils import ContextUtils
|
|
22
|
+
from entity.message.message_entity import MessageEntity
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class DataConnection:
|
|
26
|
+
"""单个数据连接"""
|
|
27
|
+
|
|
28
|
+
def __init__(self, channel_index: int, url: str, session_token: str,
|
|
29
|
+
compress_support: bool, on_message_callback: Callable):
|
|
30
|
+
self.channel_index = channel_index
|
|
31
|
+
self.url = url
|
|
32
|
+
self.session_token = session_token
|
|
33
|
+
self.compress_support = compress_support
|
|
34
|
+
self.on_message_callback = on_message_callback
|
|
35
|
+
self.ws: Optional[websocket.WebSocketApp] = None
|
|
36
|
+
self.is_connected = False
|
|
37
|
+
self.is_joined = False
|
|
38
|
+
self.running = True
|
|
39
|
+
self.thread: Optional[threading.Thread] = None
|
|
40
|
+
|
|
41
|
+
def start(self):
|
|
42
|
+
"""启动数据连接"""
|
|
43
|
+
self.thread = threading.Thread(target=self._run, daemon=True)
|
|
44
|
+
self.thread.start()
|
|
45
|
+
|
|
46
|
+
def _run(self):
|
|
47
|
+
"""连接循环"""
|
|
48
|
+
while self.running:
|
|
49
|
+
try:
|
|
50
|
+
self.ws = websocket.WebSocketApp(
|
|
51
|
+
self.url,
|
|
52
|
+
on_open=self._on_open,
|
|
53
|
+
on_message=self._on_message,
|
|
54
|
+
on_close=self._on_close,
|
|
55
|
+
on_error=self._on_error
|
|
56
|
+
)
|
|
57
|
+
self.ws.run_forever()
|
|
58
|
+
except Exception as e:
|
|
59
|
+
LoggerFactory.get_logger().error(f'Data connection {self.channel_index} error: {e}')
|
|
60
|
+
|
|
61
|
+
if self.running:
|
|
62
|
+
LoggerFactory.get_logger().info(f'Data connection {self.channel_index} reconnecting in 2s...')
|
|
63
|
+
time.sleep(2)
|
|
64
|
+
|
|
65
|
+
def _on_open(self, ws):
|
|
66
|
+
"""连接建立后发送 JOIN_SESSION"""
|
|
67
|
+
LoggerFactory.get_logger().info(f'Data connection {self.channel_index} opened, sending JOIN_SESSION')
|
|
68
|
+
self.is_connected = True
|
|
69
|
+
|
|
70
|
+
# 发送 JOIN_SESSION 消息
|
|
71
|
+
join_message: MessageEntity = {
|
|
72
|
+
'type_': MessageTypeConstant.JOIN_SESSION,
|
|
73
|
+
'data': {
|
|
74
|
+
'session_token': self.session_token,
|
|
75
|
+
'channel_index': self.channel_index
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
try:
|
|
79
|
+
ws.send(
|
|
80
|
+
NatSerialization.dumps(join_message, ContextUtils.get_password(), self.compress_support),
|
|
81
|
+
websocket.ABNF.OPCODE_BINARY
|
|
82
|
+
)
|
|
83
|
+
except Exception as e:
|
|
84
|
+
LoggerFactory.get_logger().error(f'Failed to send JOIN_SESSION: {e}')
|
|
85
|
+
|
|
86
|
+
def _on_message(self, ws, message: bytes):
|
|
87
|
+
"""处理收到的消息"""
|
|
88
|
+
try:
|
|
89
|
+
message_data: MessageEntity = NatSerialization.loads(
|
|
90
|
+
message, ContextUtils.get_password(), self.compress_support
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
# 处理 JOIN_SESSION 响应
|
|
94
|
+
if message_data['type_'] == MessageTypeConstant.JOIN_SESSION:
|
|
95
|
+
data = message_data['data']
|
|
96
|
+
if data.get('success'):
|
|
97
|
+
self.is_joined = True
|
|
98
|
+
LoggerFactory.get_logger().info(
|
|
99
|
+
f'Data connection {self.channel_index} joined session successfully'
|
|
100
|
+
)
|
|
101
|
+
else:
|
|
102
|
+
LoggerFactory.get_logger().error(
|
|
103
|
+
f'Data connection {self.channel_index} failed to join session'
|
|
104
|
+
)
|
|
105
|
+
ws.close()
|
|
106
|
+
return
|
|
107
|
+
|
|
108
|
+
# 其他消息转发给回调处理
|
|
109
|
+
if self.on_message_callback:
|
|
110
|
+
self.on_message_callback(message_data)
|
|
111
|
+
|
|
112
|
+
except Exception as e:
|
|
113
|
+
LoggerFactory.get_logger().error(f'Data connection {self.channel_index} message error: {e}')
|
|
114
|
+
LoggerFactory.get_logger().error(traceback.format_exc())
|
|
115
|
+
|
|
116
|
+
def _on_close(self, ws, code, reason):
|
|
117
|
+
"""连接关闭"""
|
|
118
|
+
LoggerFactory.get_logger().info(
|
|
119
|
+
f'Data connection {self.channel_index} closed: code={code}, reason={reason}'
|
|
120
|
+
)
|
|
121
|
+
self.is_connected = False
|
|
122
|
+
self.is_joined = False
|
|
123
|
+
|
|
124
|
+
def _on_error(self, ws, error):
|
|
125
|
+
"""连接错误"""
|
|
126
|
+
LoggerFactory.get_logger().error(f'Data connection {self.channel_index} error: {error}')
|
|
127
|
+
|
|
128
|
+
def send(self, data: bytes):
|
|
129
|
+
"""发送数据"""
|
|
130
|
+
if self.ws and self.is_joined:
|
|
131
|
+
try:
|
|
132
|
+
self.ws.send(data, websocket.ABNF.OPCODE_BINARY)
|
|
133
|
+
return True
|
|
134
|
+
except Exception as e:
|
|
135
|
+
LoggerFactory.get_logger().error(f'Data connection {self.channel_index} send error: {e}')
|
|
136
|
+
return False
|
|
137
|
+
|
|
138
|
+
def stop(self):
|
|
139
|
+
"""停止连接"""
|
|
140
|
+
self.running = False
|
|
141
|
+
self.is_connected = False
|
|
142
|
+
self.is_joined = False
|
|
143
|
+
if self.ws:
|
|
144
|
+
try:
|
|
145
|
+
self.ws.close()
|
|
146
|
+
except Exception:
|
|
147
|
+
pass
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class DataConnectionManager:
|
|
151
|
+
"""数据连接管理器"""
|
|
152
|
+
|
|
153
|
+
def __init__(self, server_url: str, compress_support: bool, on_message_callback: Callable):
|
|
154
|
+
self.server_url = server_url
|
|
155
|
+
self.compress_support = compress_support
|
|
156
|
+
self.on_message_callback = on_message_callback
|
|
157
|
+
|
|
158
|
+
self.session_token: Optional[str] = None
|
|
159
|
+
self.num_channels: int = 4
|
|
160
|
+
self.data_connections: List[DataConnection] = []
|
|
161
|
+
self.is_multi_connection: bool = False
|
|
162
|
+
self.lock = threading.Lock()
|
|
163
|
+
|
|
164
|
+
def setup_data_connections(self, session_token: str, num_channels: int = 4):
|
|
165
|
+
"""
|
|
166
|
+
设置数据连接
|
|
167
|
+
|
|
168
|
+
:param session_token: 会话令牌(hex 格式)
|
|
169
|
+
:param num_channels: 数据通道数量
|
|
170
|
+
"""
|
|
171
|
+
with self.lock:
|
|
172
|
+
# 清理旧连接
|
|
173
|
+
self.stop_all()
|
|
174
|
+
|
|
175
|
+
self.session_token = session_token
|
|
176
|
+
self.num_channels = num_channels
|
|
177
|
+
self.is_multi_connection = True
|
|
178
|
+
|
|
179
|
+
LoggerFactory.get_logger().info(
|
|
180
|
+
f'Setting up {num_channels} data connections, token={session_token[:16]}...'
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
# 创建数据连接
|
|
184
|
+
for i in range(num_channels):
|
|
185
|
+
conn = DataConnection(
|
|
186
|
+
channel_index=i,
|
|
187
|
+
url=self.server_url,
|
|
188
|
+
session_token=session_token,
|
|
189
|
+
compress_support=self.compress_support,
|
|
190
|
+
on_message_callback=self.on_message_callback
|
|
191
|
+
)
|
|
192
|
+
self.data_connections.append(conn)
|
|
193
|
+
conn.start()
|
|
194
|
+
|
|
195
|
+
LoggerFactory.get_logger().info(f'Started {num_channels} data connections')
|
|
196
|
+
|
|
197
|
+
def get_data_connection(self, uid: bytes) -> Optional[DataConnection]:
|
|
198
|
+
"""
|
|
199
|
+
根据 UID 获取数据连接
|
|
200
|
+
|
|
201
|
+
:param uid: 连接 UID
|
|
202
|
+
:return: 数据连接,如果没有可用的返回 None
|
|
203
|
+
"""
|
|
204
|
+
if not self.is_multi_connection or not self.data_connections:
|
|
205
|
+
return None
|
|
206
|
+
|
|
207
|
+
# 根据 UID hash 选择通道
|
|
208
|
+
channel_index = int.from_bytes(uid[:2], 'big') % len(self.data_connections)
|
|
209
|
+
conn = self.data_connections[channel_index]
|
|
210
|
+
|
|
211
|
+
# 检查连接是否可用
|
|
212
|
+
if conn.is_joined:
|
|
213
|
+
return conn
|
|
214
|
+
|
|
215
|
+
# 如果选中的通道不可用,尝试其他通道
|
|
216
|
+
for conn in self.data_connections:
|
|
217
|
+
if conn.is_joined:
|
|
218
|
+
return conn
|
|
219
|
+
|
|
220
|
+
return None
|
|
221
|
+
|
|
222
|
+
def send_by_uid(self, uid: bytes, data: bytes) -> bool:
|
|
223
|
+
"""
|
|
224
|
+
根据 UID 发送数据
|
|
225
|
+
|
|
226
|
+
:param uid: 连接 UID
|
|
227
|
+
:param data: 要发送的数据
|
|
228
|
+
:return: 是否成功发送
|
|
229
|
+
"""
|
|
230
|
+
conn = self.get_data_connection(uid)
|
|
231
|
+
if conn:
|
|
232
|
+
return conn.send(data)
|
|
233
|
+
return False
|
|
234
|
+
|
|
235
|
+
def is_ready(self) -> bool:
|
|
236
|
+
"""检查是否有数据连接可用"""
|
|
237
|
+
if not self.is_multi_connection:
|
|
238
|
+
return False
|
|
239
|
+
for conn in self.data_connections:
|
|
240
|
+
if conn.is_joined:
|
|
241
|
+
return True
|
|
242
|
+
return False
|
|
243
|
+
|
|
244
|
+
def stop_all(self):
|
|
245
|
+
"""停止所有数据连接"""
|
|
246
|
+
for conn in self.data_connections:
|
|
247
|
+
conn.stop()
|
|
248
|
+
self.data_connections.clear()
|
|
249
|
+
self.is_multi_connection = False
|
|
250
|
+
self.session_token = None
|
|
@@ -146,6 +146,9 @@ class TcpForwardClient:
|
|
|
146
146
|
# N4 Tunnel Manager for P2P data transfer
|
|
147
147
|
self.tunnel_manager = None
|
|
148
148
|
|
|
149
|
+
# 多连接支持:DataConnectionManager(由 run_client 注入)
|
|
150
|
+
self.data_conn_manager = None
|
|
151
|
+
|
|
149
152
|
# Pending data buffer: uid -> list of (data, timestamp)
|
|
150
153
|
# Used to buffer P2P data that arrives before connection is established
|
|
151
154
|
self.pending_data: Dict[bytes, list] = {}
|
|
@@ -295,14 +298,8 @@ class TcpForwardClient:
|
|
|
295
298
|
if not connection:
|
|
296
299
|
return
|
|
297
300
|
|
|
298
|
-
if data.speed_limiter and data.speed_limiter.is_exceed()[0]:
|
|
299
|
-
self.socket_event_loop.unregister_and_register_delay(each, data, 1)
|
|
300
|
-
return
|
|
301
|
-
|
|
302
301
|
try:
|
|
303
302
|
recv = each.recv(data.read_size)
|
|
304
|
-
if data.speed_limiter:
|
|
305
|
-
data.speed_limiter.add(len(recv))
|
|
306
303
|
except OSError:
|
|
307
304
|
recv = b''
|
|
308
305
|
|
|
@@ -311,11 +308,11 @@ class TcpForwardClient:
|
|
|
311
308
|
# 尝试走 P2P 隧道
|
|
312
309
|
if self.tunnel_manager.send_data(connection.uid, recv):
|
|
313
310
|
# 如果发送成功返回 True,逻辑结束
|
|
314
|
-
if not recv:
|
|
311
|
+
if not recv: # 本地连接关闭,通知隧道
|
|
315
312
|
self.close_connection(each)
|
|
316
313
|
return
|
|
317
314
|
|
|
318
|
-
|
|
315
|
+
# --- 回退/初始逻辑:走 WebSocket ---
|
|
319
316
|
send_message: MessageEntity = {
|
|
320
317
|
'type_': MessageTypeConstant.WEBSOCKET_OVER_TCP,
|
|
321
318
|
'data': {
|
|
@@ -326,9 +323,21 @@ class TcpForwardClient:
|
|
|
326
323
|
}
|
|
327
324
|
}
|
|
328
325
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
326
|
+
# 发送端限速:在发送前等待
|
|
327
|
+
if data.speed_limiter and recv:
|
|
328
|
+
wait_time = data.speed_limiter.acquire(len(recv))
|
|
329
|
+
if wait_time > 0:
|
|
330
|
+
time.sleep(wait_time)
|
|
331
|
+
|
|
332
|
+
# 多连接模式:优先使用数据连接
|
|
333
|
+
message_bytes = NatSerialization.dumps(send_message, ContextUtils.get_password(), self.compress_support)
|
|
334
|
+
if self.data_conn_manager and self.data_conn_manager.is_ready():
|
|
335
|
+
if not self.data_conn_manager.send_by_uid(connection.uid, message_bytes):
|
|
336
|
+
# 数据连接发送失败,回退到控制连接
|
|
337
|
+
connection.sender.enqueue_message(message_bytes)
|
|
338
|
+
else:
|
|
339
|
+
# 单连接模式:使用控制连接
|
|
340
|
+
connection.sender.enqueue_message(message_bytes)
|
|
332
341
|
|
|
333
342
|
if not recv:
|
|
334
343
|
try:
|
|
@@ -487,7 +496,16 @@ class TcpForwardClient:
|
|
|
487
496
|
}
|
|
488
497
|
}
|
|
489
498
|
start_time = time.time()
|
|
490
|
-
|
|
499
|
+
message_bytes = NatSerialization.dumps(send_message, ContextUtils.get_password(), self.compress_support)
|
|
500
|
+
|
|
501
|
+
# 多连接模式:优先使用数据连接
|
|
502
|
+
if self.data_conn_manager and self.data_conn_manager.is_ready():
|
|
503
|
+
if not self.data_conn_manager.send_by_uid(connection.uid, message_bytes):
|
|
504
|
+
# 数据连接发送失败,回退到控制连接
|
|
505
|
+
self.ws.send(message_bytes, websocket.ABNF.OPCODE_BINARY)
|
|
506
|
+
else:
|
|
507
|
+
# 单连接模式
|
|
508
|
+
self.ws.send(message_bytes, websocket.ABNF.OPCODE_BINARY)
|
|
491
509
|
LoggerFactory.get_logger().debug(f'Send to websocket cost time {time.time() - start_time}')
|
|
492
510
|
|
|
493
511
|
def send_by_uid(self, uid: bytes, msg: bytes):
|
|
@@ -53,6 +53,9 @@ class UdpForwardClient:
|
|
|
53
53
|
self.c2c_listeners: Dict[str, socket.socket] = {} # rule_name → listener socket
|
|
54
54
|
self.c2c_uid_to_rule: Dict[bytes, str] = {} # UID → rule_name
|
|
55
55
|
|
|
56
|
+
# 多连接支持:DataConnectionManager(由 run_client 注入)
|
|
57
|
+
self.data_conn_manager = None
|
|
58
|
+
|
|
56
59
|
def set_running(self, running: bool):
|
|
57
60
|
"""Set running state"""
|
|
58
61
|
self.running = running
|
|
@@ -131,6 +134,9 @@ class UdpForwardClient:
|
|
|
131
134
|
protocol = rule['protocol']
|
|
132
135
|
speed_limit = rule.get('speed_limit', 0.0)
|
|
133
136
|
|
|
137
|
+
# 创建限速器
|
|
138
|
+
speed_limiter = SpeedLimiter(speed_limit) if speed_limit > 0 else None
|
|
139
|
+
|
|
134
140
|
# Check if using direct mode (target_ip + target_port) or service mode (target_service)
|
|
135
141
|
use_direct_mode = 'target_ip' in rule and 'target_port' in rule
|
|
136
142
|
|
|
@@ -205,10 +211,22 @@ class UdpForwardClient:
|
|
|
205
211
|
'ip_port': f"{source_addr[0]}:{source_addr[1]}"
|
|
206
212
|
}
|
|
207
213
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
214
|
+
|
|
215
|
+
# 发送端限速:在发送前等待
|
|
216
|
+
if speed_limiter and data:
|
|
217
|
+
wait_time = speed_limiter.acquire(len(data))
|
|
218
|
+
if wait_time > 0:
|
|
219
|
+
time.sleep(wait_time)
|
|
220
|
+
|
|
221
|
+
message_bytes = NatSerialization.dumps(send_message, ContextUtils.get_password(), self.compress_support)
|
|
222
|
+
# 多连接模式:优先使用数据连接
|
|
223
|
+
if self.data_conn_manager and self.data_conn_manager.is_ready():
|
|
224
|
+
if not self.data_conn_manager.send_by_uid(uid, message_bytes):
|
|
225
|
+
# 数据连接发送失败,回退到控制连接
|
|
226
|
+
self.ws.send(message_bytes, websocket.ABNF.OPCODE_BINARY)
|
|
227
|
+
else:
|
|
228
|
+
# 单连接模式
|
|
229
|
+
self.ws.send(message_bytes, websocket.ABNF.OPCODE_BINARY)
|
|
212
230
|
LoggerFactory.get_logger().debug(f'C2C UDP data forwarded: {rule_name} UID: {uid.hex()}, len: {len(data)}')
|
|
213
231
|
|
|
214
232
|
except OSError as e:
|
|
@@ -246,15 +264,6 @@ class UdpForwardClient:
|
|
|
246
264
|
|
|
247
265
|
def _handle_udp_data(self, conn: UdpSocketConnection, data: bytes, addr):
|
|
248
266
|
"""Handle received UDP data"""
|
|
249
|
-
# Speed limit handling
|
|
250
|
-
if conn.speed_limiter and conn.speed_limiter.is_exceed()[0]:
|
|
251
|
-
if LoggerFactory.get_logger().isEnabledFor(logging.DEBUG):
|
|
252
|
-
LoggerFactory.get_logger().debug('UDP speed limit exceeded')
|
|
253
|
-
return
|
|
254
|
-
|
|
255
|
-
if conn.speed_limiter:
|
|
256
|
-
conn.speed_limiter.add(len(data))
|
|
257
|
-
|
|
258
267
|
# Construct UDP message and send to public server via WebSocket
|
|
259
268
|
send_message: MessageEntity = {
|
|
260
269
|
'type_': MessageTypeConstant.WEBSOCKET_OVER_UDP,
|
|
@@ -270,7 +279,21 @@ class UdpForwardClient:
|
|
|
270
279
|
LoggerFactory.get_logger().debug(f'Sending UDP to WebSocket, uid: {conn.uid}, len: {len(data)}')
|
|
271
280
|
|
|
272
281
|
try:
|
|
273
|
-
|
|
282
|
+
# 发送端限速:在发送前等待
|
|
283
|
+
if conn.speed_limiter and data:
|
|
284
|
+
wait_time = conn.speed_limiter.acquire(len(data))
|
|
285
|
+
if wait_time > 0:
|
|
286
|
+
time.sleep(wait_time)
|
|
287
|
+
|
|
288
|
+
message_bytes = NatSerialization.dumps(send_message, ContextUtils.get_password(), self.compress_support)
|
|
289
|
+
# 多连接模式:优先使用数据连接
|
|
290
|
+
if self.data_conn_manager and self.data_conn_manager.is_ready():
|
|
291
|
+
if not self.data_conn_manager.send_by_uid(conn.uid, message_bytes):
|
|
292
|
+
# 数据连接发送失败,回退到控制连接
|
|
293
|
+
self.ws.send(message_bytes, websocket.ABNF.OPCODE_BINARY)
|
|
294
|
+
else:
|
|
295
|
+
# 单连接模式
|
|
296
|
+
self.ws.send(message_bytes, websocket.ABNF.OPCODE_BINARY)
|
|
274
297
|
except Exception as e:
|
|
275
298
|
LoggerFactory.get_logger().error(f"Failed to send UDP to WebSocket: {e}")
|
|
276
299
|
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import threading
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class SpeedLimiter:
|
|
6
|
+
"""
|
|
7
|
+
令牌桶限速器(发送端限速)
|
|
8
|
+
|
|
9
|
+
使用方式:
|
|
10
|
+
limiter = SpeedLimiter(max_speed_mb=1.0) # 1 MB/s
|
|
11
|
+
|
|
12
|
+
# 同步使用
|
|
13
|
+
wait_time = limiter.acquire(len(data))
|
|
14
|
+
if wait_time > 0:
|
|
15
|
+
time.sleep(wait_time)
|
|
16
|
+
send(data)
|
|
17
|
+
|
|
18
|
+
# 异步使用
|
|
19
|
+
wait_time = limiter.acquire(len(data))
|
|
20
|
+
if wait_time > 0:
|
|
21
|
+
await asyncio.sleep(wait_time)
|
|
22
|
+
await send(data)
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, max_speed_mb=0):
|
|
26
|
+
"""
|
|
27
|
+
:param max_speed_mb: 最大速度,单位 MB/s,0 表示不限速
|
|
28
|
+
"""
|
|
29
|
+
self.max_speed = max_speed_mb * 1024 * 1024 # 转换为字节/秒
|
|
30
|
+
self.tokens = self.max_speed # 初始令牌数(允许突发)
|
|
31
|
+
self.last_time = time.time()
|
|
32
|
+
self.lock = threading.Lock()
|
|
33
|
+
|
|
34
|
+
def acquire(self, data_len):
|
|
35
|
+
"""
|
|
36
|
+
获取发送许可
|
|
37
|
+
|
|
38
|
+
:param data_len: 要发送的数据长度(字节)
|
|
39
|
+
:return: 需要等待的时间(秒),0 表示不需要等待
|
|
40
|
+
"""
|
|
41
|
+
if self.max_speed <= 0:
|
|
42
|
+
return 0
|
|
43
|
+
|
|
44
|
+
with self.lock:
|
|
45
|
+
now = time.time()
|
|
46
|
+
elapsed = now - self.last_time
|
|
47
|
+
self.last_time = now
|
|
48
|
+
|
|
49
|
+
# 补充令牌(按时间流逝补充,最多补充到 max_speed)
|
|
50
|
+
self.tokens = min(self.max_speed, self.tokens + elapsed * self.max_speed)
|
|
51
|
+
|
|
52
|
+
# 消耗令牌
|
|
53
|
+
self.tokens -= data_len
|
|
54
|
+
|
|
55
|
+
if self.tokens >= 0:
|
|
56
|
+
return 0 # 有足够令牌,不需要等待
|
|
57
|
+
|
|
58
|
+
# 令牌不足,计算需要等待的时间
|
|
59
|
+
wait_time = -self.tokens / self.max_speed
|
|
60
|
+
return wait_time
|
|
61
|
+
|
|
62
|
+
def set_max_speed(self, max_speed_mb):
|
|
63
|
+
"""
|
|
64
|
+
动态调整限速
|
|
65
|
+
|
|
66
|
+
:param max_speed_mb: 新的最大速度,单位 MB/s
|
|
67
|
+
"""
|
|
68
|
+
with self.lock:
|
|
69
|
+
self.max_speed = max_speed_mb * 1024 * 1024
|
|
70
|
+
# 重置令牌,避免突然加速
|
|
71
|
+
self.tokens = min(self.tokens, self.max_speed)
|
|
72
|
+
|
|
73
|
+
# 保留旧接口的兼容性(但不推荐使用)
|
|
74
|
+
def add(self, data_len):
|
|
75
|
+
"""兼容旧接口,等同于 acquire 但不返回等待时间"""
|
|
76
|
+
self.acquire(data_len)
|
|
77
|
+
|
|
78
|
+
def is_exceed(self):
|
|
79
|
+
"""兼容旧接口,返回 (是否超速, 剩余量)"""
|
|
80
|
+
if self.max_speed <= 0:
|
|
81
|
+
return False, 0
|
|
82
|
+
with self.lock:
|
|
83
|
+
now = time.time()
|
|
84
|
+
elapsed = now - self.last_time
|
|
85
|
+
# 不更新 last_time,只是检查
|
|
86
|
+
current_tokens = min(self.max_speed, self.tokens + elapsed * self.max_speed)
|
|
87
|
+
if current_tokens >= 0:
|
|
88
|
+
return False, current_tokens
|
|
89
|
+
return True, -current_tokens
|
|
@@ -28,4 +28,7 @@ class MessageTypeConstant:
|
|
|
28
28
|
P2P_PRE_CONNECT = 'g'
|
|
29
29
|
P2P_EXCHANGE = 'h' # UDP exchange to establish NAT mapping
|
|
30
30
|
P2P_PEER_INFO = 'i' # Server sends peer's actual UDP port
|
|
31
|
-
P2P_PUNCH_REQUEST = 'j' # Request to initiate P2P hole punching
|
|
31
|
+
P2P_PUNCH_REQUEST = 'j' # Request to initiate P2P hole punching
|
|
32
|
+
|
|
33
|
+
# 多连接支持
|
|
34
|
+
JOIN_SESSION = 'k' # 数据连接加入会话(携带 session_token)
|
|
@@ -8,6 +8,7 @@ setup.py
|
|
|
8
8
|
./client/__init__.py
|
|
9
9
|
./client/abstract_tunnel.py
|
|
10
10
|
./client/clear_nonce_task.py
|
|
11
|
+
./client/data_connection_manager.py
|
|
11
12
|
./client/heart_beat_task.py
|
|
12
13
|
./client/kcp_tunnel_impl.py
|
|
13
14
|
./client/n4_tunnel_manager.py
|
|
@@ -74,6 +75,7 @@ setup.py
|
|
|
74
75
|
./server/admin_http_handler.py
|
|
75
76
|
./server/n4.py
|
|
76
77
|
./server/n4_signal_service.py
|
|
78
|
+
./server/session_manager.py
|
|
77
79
|
./server/tcp_forward_client.py
|
|
78
80
|
./server/udp_forward_client.py
|
|
79
81
|
./server/websocket_handler.py
|