proxynt 2.0.40__tar.gz → 2.0.44__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.40 → proxynt-2.0.44}/PKG-INFO +1 -1
- {proxynt-2.0.40 → proxynt-2.0.44}/client/heart_beat_task.py +3 -2
- {proxynt-2.0.40 → proxynt-2.0.44}/client/tcp_forward_client.py +7 -6
- {proxynt-2.0.40 → proxynt-2.0.44}/client/udp_forward_client.py +5 -4
- proxynt-2.0.44/common/nat_serialization.py +129 -0
- proxynt-2.0.40/common/nat_serialization.py → proxynt-2.0.44/common/nat_serialization_v1.py +35 -75
- proxynt-2.0.44/common/nat_serialization_v2.py +107 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/constant/system_constant.py +1 -1
- {proxynt-2.0.40 → proxynt-2.0.44}/entity/message/push_config_entity.py +1 -0
- proxynt-2.0.44/entity/rule_entity.py +28 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/proxynt.egg-info/PKG-INFO +1 -1
- {proxynt-2.0.40 → proxynt-2.0.44}/proxynt.egg-info/SOURCES.txt +3 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/proxynt.egg-info/requires.txt +1 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/run_client.py +17 -6
- {proxynt-2.0.40 → proxynt-2.0.44}/server/admin_http_handler.py +2 -1
- {proxynt-2.0.40 → proxynt-2.0.44}/server/n4_signal_service.py +2 -2
- {proxynt-2.0.40 → proxynt-2.0.44}/server/task/heart_beat_task.py +1 -1
- {proxynt-2.0.40 → proxynt-2.0.44}/server/tcp_forward_client.py +6 -3
- {proxynt-2.0.40 → proxynt-2.0.44}/server/template/ele_index.html +9 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/server/udp_forward_client.py +4 -2
- {proxynt-2.0.40 → proxynt-2.0.44}/server/websocket_handler.py +26 -18
- {proxynt-2.0.40 → proxynt-2.0.44}/setup.py +1 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/LICENSE +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/MANIFEST.in +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/__init__.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/client/__init__.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/client/abstract_tunnel.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/client/clear_nonce_task.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/client/kcp_tunnel_impl.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/client/n4_tunnel_manager.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/client/quic_tunnel_impl.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/client/tunnel_protocol.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/common/__init__.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/common/cert_utils.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/common/crypto/__init__.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/common/crypto/table.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/common/encrypt_utils.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/common/kcp.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/common/logger_factory.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/common/n4_protocol.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/common/n4_punch.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/common/pool.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/common/register_append_data.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/common/speed_limit.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/common/websocket/__init__.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/common/websocket/_abnf.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/common/websocket/_app.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/common/websocket/_cookiejar.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/common/websocket/_core.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/common/websocket/_exceptions.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/common/websocket/_handshake.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/common/websocket/_http.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/common/websocket/_logging.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/common/websocket/_socket.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/common/websocket/_ssl_compat.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/common/websocket/_url.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/common/websocket/_utils.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/common/websocket/_wsdump.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/common/websocket/tests/__init__.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/common/websocket/tests/echo-server.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/common/websocket/tests/test_abnf.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/common/websocket/tests/test_app.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/common/websocket/tests/test_cookiejar.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/common/websocket/tests/test_http.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/common/websocket/tests/test_url.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/common/websocket/tests/test_websocket.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/constant/__init__.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/constant/message_type_constnat.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/context/__init__.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/context/context_utils.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/entity/__init__.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/entity/client_config_entity.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/entity/message/__init__.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/entity/message/message_entity.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/entity/message/tcp_over_websocket_message.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/entity/server_config_entity.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/exceptions/__init__.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/exceptions/duplicated_name.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/exceptions/invalid_password.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/exceptions/replay_error.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/exceptions/signature_error.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/p2ptest/__init__.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/p2ptest/client.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/p2ptest/n4.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/proxynt.egg-info/dependency_links.txt +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/proxynt.egg-info/entry_points.txt +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/proxynt.egg-info/top_level.txt +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/run_server.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/server/__init__.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/server/n4.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/server/task/__init__.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/server/task/check_cookie_task.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/server/task/clear_nonce_task.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/server/template/__init__.py +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/server/template/base.html +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/server/template/css/fonts/element-icons.woff +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/server/template/css/index.css +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/server/template/js/axios.min.js +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/server/template/js/index.js +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/server/template/js/vue.min.js +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/server/template/login.html +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/setup.cfg +0 -0
- {proxynt-2.0.40 → proxynt-2.0.44}/test_exchange.py +0 -0
|
@@ -15,11 +15,12 @@ from entity.message.message_entity import MessageEntity
|
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
class HeatBeatTask:
|
|
18
|
-
def __init__(self, ws: websocket.WebSocketApp, sleep_break: int,):
|
|
18
|
+
def __init__(self, ws: websocket.WebSocketApp, sleep_break: int, protocol_version: int):
|
|
19
19
|
self.ws: websocket.WebSocketApp = ws
|
|
20
20
|
self.is_running = False
|
|
21
21
|
self.recv_heart_beat_time: float = time.time()
|
|
22
22
|
self.sleep_break = sleep_break
|
|
23
|
+
self.protocol_version = protocol_version
|
|
23
24
|
|
|
24
25
|
def set_recv_heart_beat_time(self, d: float):
|
|
25
26
|
self.recv_heart_beat_time = d
|
|
@@ -57,7 +58,7 @@ class HeatBeatTask:
|
|
|
57
58
|
}
|
|
58
59
|
# 心跳消息直接发送(不通过队列),确保最低延迟
|
|
59
60
|
# 因为客户端心跳不经过 MessageSender,直接使用 ws.send
|
|
60
|
-
self.ws.send(NatSerialization.dumps(ping_message, ContextUtils.get_password(), False), websocket.ABNF.OPCODE_BINARY)
|
|
61
|
+
self.ws.send(NatSerialization.dumps(ping_message, ContextUtils.get_password(), False, self.protocol_version), websocket.ABNF.OPCODE_BINARY)
|
|
61
62
|
# LoggerFactory.get_logger().debug('send client heart beat success ')
|
|
62
63
|
else:
|
|
63
64
|
pass
|
|
@@ -130,10 +130,11 @@ class PrivateSocketConnection:
|
|
|
130
130
|
|
|
131
131
|
|
|
132
132
|
class TcpForwardClient:
|
|
133
|
-
def __init__(self, ws: websocket, compress_support: bool):
|
|
133
|
+
def __init__(self, ws: websocket, compress_support: bool, protocol_version: int):
|
|
134
134
|
self.uid_to_socket_connection: Dict[bytes, PrivateSocketConnection] = dict()
|
|
135
135
|
self.socket_to_socket_connection: Dict[socket.socket, PrivateSocketConnection] = dict()
|
|
136
136
|
self.compress_support: bool = compress_support
|
|
137
|
+
self.protocol_version: int = protocol_version
|
|
137
138
|
self.ws = ws
|
|
138
139
|
self.lock = Lock()
|
|
139
140
|
self.socket_event_loop = SelectPool()
|
|
@@ -272,7 +273,7 @@ class TcpForwardClient:
|
|
|
272
273
|
'data': forward_data
|
|
273
274
|
}
|
|
274
275
|
self.ws.send(
|
|
275
|
-
NatSerialization.dumps(forward_message, ContextUtils.get_password(), self.compress_support),
|
|
276
|
+
NatSerialization.dumps(forward_message, ContextUtils.get_password(), self.compress_support, self.protocol_version),
|
|
276
277
|
websocket.ABNF.OPCODE_BINARY
|
|
277
278
|
)
|
|
278
279
|
LoggerFactory.get_logger().info(f'C2C forward request sent: {rule_name} UID: {uid.hex()}')
|
|
@@ -327,7 +328,7 @@ class TcpForwardClient:
|
|
|
327
328
|
}
|
|
328
329
|
|
|
329
330
|
connection.sender.enqueue_message(
|
|
330
|
-
NatSerialization.dumps(send_message, ContextUtils.get_password(), self.compress_support)
|
|
331
|
+
NatSerialization.dumps(send_message, ContextUtils.get_password(), self.compress_support, self.protocol_version)
|
|
331
332
|
)
|
|
332
333
|
|
|
333
334
|
if not recv:
|
|
@@ -372,7 +373,7 @@ class TcpForwardClient:
|
|
|
372
373
|
'ip_port': ip_port
|
|
373
374
|
}
|
|
374
375
|
}
|
|
375
|
-
self.ws.send(NatSerialization.dumps(confirm_message, ContextUtils.get_password(), self.compress_support), websocket.ABNF.OPCODE_BINARY)
|
|
376
|
+
self.ws.send(NatSerialization.dumps(confirm_message, ContextUtils.get_password(), self.compress_support, self.protocol_version), websocket.ABNF.OPCODE_BINARY)
|
|
376
377
|
if LoggerFactory.get_logger().isEnabledFor(logging.DEBUG):
|
|
377
378
|
LoggerFactory.get_logger().debug(f'Connection confirmation message sent uid: {uid}')
|
|
378
379
|
|
|
@@ -390,7 +391,7 @@ class TcpForwardClient:
|
|
|
390
391
|
}
|
|
391
392
|
}
|
|
392
393
|
try:
|
|
393
|
-
self.ws.send(NatSerialization.dumps(fail_message, ContextUtils.get_password(), self.compress_support), websocket.ABNF.OPCODE_BINARY)
|
|
394
|
+
self.ws.send(NatSerialization.dumps(fail_message, ContextUtils.get_password(), self.compress_support, self.protocol_version), websocket.ABNF.OPCODE_BINARY)
|
|
394
395
|
except Exception as send_err:
|
|
395
396
|
LoggerFactory.get_logger().error(f'Failed to send connection failure message: {send_err}')
|
|
396
397
|
|
|
@@ -487,7 +488,7 @@ class TcpForwardClient:
|
|
|
487
488
|
}
|
|
488
489
|
}
|
|
489
490
|
start_time = time.time()
|
|
490
|
-
self.ws.send(NatSerialization.dumps(send_message, ContextUtils.get_password(), self.compress_support), websocket.ABNF.OPCODE_BINARY)
|
|
491
|
+
self.ws.send(NatSerialization.dumps(send_message, ContextUtils.get_password(), self.compress_support, self.protocol_version), websocket.ABNF.OPCODE_BINARY)
|
|
491
492
|
LoggerFactory.get_logger().debug(f'Send to websocket cost time {time.time() - start_time}')
|
|
492
493
|
|
|
493
494
|
def send_by_uid(self, uid: bytes, msg: bytes):
|
|
@@ -38,10 +38,11 @@ class UdpSocketConnection:
|
|
|
38
38
|
|
|
39
39
|
class UdpForwardClient:
|
|
40
40
|
"""UDP forward client"""
|
|
41
|
-
def __init__(self, ws, compress_support: bool):
|
|
41
|
+
def __init__(self, ws, compress_support: bool, protocol_version: int):
|
|
42
42
|
self.uid_to_connection: Dict[bytes, UdpSocketConnection] = {}
|
|
43
43
|
self.ws = ws
|
|
44
44
|
self.compress_support = compress_support
|
|
45
|
+
self.protocol_version = protocol_version
|
|
45
46
|
self.running = True
|
|
46
47
|
self.lock = threading.Lock()
|
|
47
48
|
|
|
@@ -177,7 +178,7 @@ class UdpForwardClient:
|
|
|
177
178
|
'data': forward_data
|
|
178
179
|
}
|
|
179
180
|
self.ws.send(
|
|
180
|
-
NatSerialization.dumps(forward_message, ContextUtils.get_password(), self.compress_support),
|
|
181
|
+
NatSerialization.dumps(forward_message, ContextUtils.get_password(), self.compress_support, self.protocol_version),
|
|
181
182
|
websocket.ABNF.OPCODE_BINARY
|
|
182
183
|
)
|
|
183
184
|
LoggerFactory.get_logger().info(f'C2C UDP forward request sent: {rule_name} UID: {uid.hex()}')
|
|
@@ -206,7 +207,7 @@ class UdpForwardClient:
|
|
|
206
207
|
}
|
|
207
208
|
}
|
|
208
209
|
self.ws.send(
|
|
209
|
-
NatSerialization.dumps(send_message, ContextUtils.get_password(), self.compress_support),
|
|
210
|
+
NatSerialization.dumps(send_message, ContextUtils.get_password(), self.compress_support, self.protocol_version),
|
|
210
211
|
websocket.ABNF.OPCODE_BINARY
|
|
211
212
|
)
|
|
212
213
|
LoggerFactory.get_logger().debug(f'C2C UDP data forwarded: {rule_name} UID: {uid.hex()}, len: {len(data)}')
|
|
@@ -267,7 +268,7 @@ class UdpForwardClient:
|
|
|
267
268
|
LoggerFactory.get_logger().debug(f'Sending UDP to WebSocket, uid: {conn.uid}, len: {len(data)}')
|
|
268
269
|
|
|
269
270
|
try:
|
|
270
|
-
self.ws.send(NatSerialization.dumps(send_message, ContextUtils.get_password(), self.compress_support), websocket.ABNF.OPCODE_BINARY)
|
|
271
|
+
self.ws.send(NatSerialization.dumps(send_message, ContextUtils.get_password(), self.compress_support, self.protocol_version), websocket.ABNF.OPCODE_BINARY)
|
|
271
272
|
except Exception as e:
|
|
272
273
|
LoggerFactory.get_logger().error(f"Failed to send UDP to WebSocket: {e}")
|
|
273
274
|
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""
|
|
2
|
+
NatSerialization - Version dispatcher
|
|
3
|
+
|
|
4
|
+
根据 protocol_version 选择对应的序列化实现:
|
|
5
|
+
- V1: 二进制格式(原始实现,兼容旧客户端)
|
|
6
|
+
- V2: msgpack 格式(灵活,支持任意字段)
|
|
7
|
+
"""
|
|
8
|
+
import os
|
|
9
|
+
import struct
|
|
10
|
+
|
|
11
|
+
from common.nat_serialization_v1 import NatSerializationV1
|
|
12
|
+
from common.nat_serialization_v2 import NatSerializationV2
|
|
13
|
+
from context.context_utils import ContextUtils
|
|
14
|
+
from entity.message.message_entity import MessageEntity
|
|
15
|
+
|
|
16
|
+
# 默认协议版本
|
|
17
|
+
DEFAULT_PROTOCOL_VERSION = 1
|
|
18
|
+
|
|
19
|
+
# 版本到序列化器的映射
|
|
20
|
+
VERSION_TO_SERIALIZER = {
|
|
21
|
+
1: NatSerializationV1,
|
|
22
|
+
2: NatSerializationV2,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class NatSerialization:
|
|
27
|
+
"""
|
|
28
|
+
序列化入口类 - 根据版本分发到对应的实现
|
|
29
|
+
|
|
30
|
+
使用方法:
|
|
31
|
+
- dumps/loads 默认使用 V1(向后兼容)
|
|
32
|
+
- 可以通过 protocol_version 参数指定版本
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
@classmethod
|
|
36
|
+
def get_serializer(cls, protocol_version: int = DEFAULT_PROTOCOL_VERSION):
|
|
37
|
+
"""获取指定版本的序列化器"""
|
|
38
|
+
serializer = VERSION_TO_SERIALIZER.get(protocol_version)
|
|
39
|
+
if serializer is None:
|
|
40
|
+
raise ValueError(f"Unsupported protocol version: {protocol_version}")
|
|
41
|
+
return serializer
|
|
42
|
+
|
|
43
|
+
@classmethod
|
|
44
|
+
def dumps(cls, data: MessageEntity, key: str, compress: bool, protocol_version: int = DEFAULT_PROTOCOL_VERSION) -> bytes:
|
|
45
|
+
"""
|
|
46
|
+
序列化消息
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
data: 消息实体
|
|
50
|
+
key: 加密密钥
|
|
51
|
+
compress: 是否压缩
|
|
52
|
+
protocol_version: 协议版本 (1=二进制, 2=msgpack)
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
序列化后的字节数据
|
|
56
|
+
"""
|
|
57
|
+
serializer = cls.get_serializer(protocol_version)
|
|
58
|
+
return serializer.dumps(data, key, compress)
|
|
59
|
+
|
|
60
|
+
@classmethod
|
|
61
|
+
def loads(cls, byte_data: bytes, key: str, compress: bool, protocol_version: int = DEFAULT_PROTOCOL_VERSION) -> MessageEntity:
|
|
62
|
+
"""
|
|
63
|
+
反序列化消息
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
byte_data: 字节数据
|
|
67
|
+
key: 加密密钥
|
|
68
|
+
compress: 是否解压
|
|
69
|
+
protocol_version: 协议版本 (1=二进制, 2=msgpack)
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
消息实体
|
|
73
|
+
"""
|
|
74
|
+
serializer = cls.get_serializer(protocol_version)
|
|
75
|
+
return serializer.loads(byte_data, key, compress)
|
|
76
|
+
|
|
77
|
+
# 保持向后兼容的方法
|
|
78
|
+
@classmethod
|
|
79
|
+
def check_signature(cls, clear_text: bytes, data_len: int, key: str) -> bool:
|
|
80
|
+
"""检查签名(使用 V1 实现)"""
|
|
81
|
+
return NatSerializationV1.check_signature(clear_text, data_len, key)
|
|
82
|
+
|
|
83
|
+
@classmethod
|
|
84
|
+
def check_nonce_and_timestamp(cls, clear_text: bytes) -> bool:
|
|
85
|
+
"""检查 nonce 和时间戳(使用 V1 实现)"""
|
|
86
|
+
return NatSerializationV1.check_nonce_and_timestamp(clear_text)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# 保持向后兼容的常量
|
|
90
|
+
UID_LEN = 4
|
|
91
|
+
HEADER_LEN = 22
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
if __name__ == '__main__':
|
|
95
|
+
ContextUtils.set_nonce_to_time({})
|
|
96
|
+
|
|
97
|
+
def _print_commend(msg):
|
|
98
|
+
print(''.join("b'{}'".format(''.join('\\x{:02x}'.format(b) for b in msg))))
|
|
99
|
+
|
|
100
|
+
# 测试数据
|
|
101
|
+
data = {
|
|
102
|
+
'type_': 'a',
|
|
103
|
+
'data': {
|
|
104
|
+
'name': 'ssh',
|
|
105
|
+
'uid': os.urandom(4),
|
|
106
|
+
'ip_port': '127.0.0.1:8888',
|
|
107
|
+
'data': b'test data',
|
|
108
|
+
'source_client': 'client1', # V2 支持的额外字段
|
|
109
|
+
'speed_limit': 1024.0, # V2 支持的额外字段
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
key32 = 'xxxx'
|
|
113
|
+
|
|
114
|
+
print("=== V1 测试 (二进制格式) ===")
|
|
115
|
+
try:
|
|
116
|
+
a1 = NatSerialization.dumps(data, key32, False, protocol_version=1)
|
|
117
|
+
print(f"V1 序列化大小: {len(a1)} bytes")
|
|
118
|
+
b1 = NatSerialization.loads(a1, key32, False, protocol_version=1)
|
|
119
|
+
print(f"V1 反序列化: {b1}")
|
|
120
|
+
print(f"注意: V1 不支持 source_client 和 speed_limit 字段")
|
|
121
|
+
except Exception as e:
|
|
122
|
+
print(f"V1 错误: {e}")
|
|
123
|
+
|
|
124
|
+
print("\n=== V2 测试 (msgpack 格式) ===")
|
|
125
|
+
a2 = NatSerialization.dumps(data, key32, False, protocol_version=2)
|
|
126
|
+
print(f"V2 序列化大小: {len(a2)} bytes")
|
|
127
|
+
b2 = NatSerialization.loads(a2, key32, False, protocol_version=2)
|
|
128
|
+
print(f"V2 反序列化: {b2}")
|
|
129
|
+
print(f"V2 支持所有字段,包括 source_client={b2['data'].get('source_client')}, speed_limit={b2['data'].get('speed_limit')}")
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
"""
|
|
2
|
+
NatSerialization V1 - Binary format (original implementation)
|
|
3
|
+
"""
|
|
1
4
|
import json
|
|
2
5
|
import os
|
|
3
6
|
import struct
|
|
@@ -11,59 +14,52 @@ except ModuleNotFoundError:
|
|
|
11
14
|
|
|
12
15
|
from common.encrypt_utils import EncryptUtils
|
|
13
16
|
from constant.message_type_constnat import MessageTypeConstant
|
|
14
|
-
from constant.system_constant import SystemConstant
|
|
15
|
-
from context.context_utils import ContextUtils
|
|
16
17
|
from entity.message.message_entity import MessageEntity
|
|
17
18
|
from entity.message.tcp_over_websocket_message import TcpOverWebsocketMessage
|
|
18
|
-
from exceptions.replay_error import ReplayError
|
|
19
|
-
from exceptions.signature_error import SignatureError
|
|
20
19
|
|
|
21
20
|
UID_LEN = 4
|
|
22
|
-
HEADER_LEN = 22 # xxHash64 模式: 22
|
|
21
|
+
HEADER_LEN = 22 # xxHash64 模式: 22 字节
|
|
23
22
|
|
|
24
23
|
|
|
25
|
-
class
|
|
24
|
+
class NatSerializationV1:
|
|
26
25
|
"""
|
|
27
|
-
|
|
26
|
+
V1 序列化格式 - 二进制格式(原始实现)
|
|
28
27
|
|
|
29
|
-
|
|
30
|
-
类型(1字节) | body长度(4字节) | 随机字符串(5字节) | 时间戳(4字节) | 签名 (8 字节)
|
|
28
|
+
header + body
|
|
31
29
|
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
header (22 字节) - 使用 xxHash64 签名:
|
|
31
|
+
类型(1字节) | body长度(4字节) | 随机字符串(5字节) | 时间戳(4字节) | 签名 (8 字节)
|
|
34
32
|
|
|
35
|
-
|
|
33
|
+
body (长度不固定):
|
|
34
|
+
实际数据
|
|
36
35
|
"""
|
|
37
36
|
|
|
38
|
-
# 报文形式: 类型, 数据
|
|
39
37
|
@classmethod
|
|
40
38
|
def dumps(cls, data: MessageEntity, key: str, compress: bool) -> bytes:
|
|
41
39
|
type_ = data['type_']
|
|
42
40
|
if type_ in (MessageTypeConstant.WEBSOCKET_OVER_TCP, MessageTypeConstant.REQUEST_TO_CONNECT,
|
|
43
41
|
MessageTypeConstant.WEBSOCKET_OVER_UDP, MessageTypeConstant.REQUEST_TO_CONNECT_UDP,
|
|
44
|
-
MessageTypeConstant.CONNECT_CONFIRMED, MessageTypeConstant.CONNECT_FAILED):
|
|
42
|
+
MessageTypeConstant.CONNECT_CONFIRMED, MessageTypeConstant.CONNECT_FAILED):
|
|
45
43
|
data_content: TcpOverWebsocketMessage = data['data']
|
|
46
|
-
uid = data_content['uid']
|
|
44
|
+
uid = data_content['uid']
|
|
47
45
|
name = data_content['name']
|
|
48
46
|
if compress:
|
|
49
47
|
bytes_: TcpOverWebsocketMessage = snappy.snappy.compress(data_content['data'])
|
|
50
48
|
else:
|
|
51
49
|
bytes_ = data_content['data']
|
|
52
50
|
ip_port = data_content['ip_port']
|
|
53
|
-
body = struct.pack(f'BBI{UID_LEN}s{len(name.encode())}s{len(ip_port)}s{len(bytes_)}s',
|
|
51
|
+
body = struct.pack(f'BBI{UID_LEN}s{len(name.encode())}s{len(ip_port)}s{len(bytes_)}s',
|
|
52
|
+
len(name.encode()), len(ip_port), len(bytes_), uid, name.encode(), ip_port.encode(), bytes_)
|
|
54
53
|
|
|
55
54
|
elif type_ == MessageTypeConstant.CLIENT_TO_CLIENT_FORWARD:
|
|
56
|
-
# C2C forward request: support two modes
|
|
57
55
|
data_content = data['data']
|
|
58
56
|
uid = data_content['uid']
|
|
59
57
|
target_client = data_content['target_client'].encode()
|
|
60
58
|
source_rule_name = data_content['source_rule_name'].encode()
|
|
61
59
|
protocol = data_content['protocol'].encode()
|
|
62
60
|
|
|
63
|
-
# Check if using direct mode (target_ip + target_port) or service mode (target_service)
|
|
64
61
|
if 'target_ip' in data_content and 'target_port' in data_content:
|
|
65
|
-
|
|
66
|
-
magic = 0xFF # Magic number to identify new format
|
|
62
|
+
magic = 0xFF
|
|
67
63
|
mode_flag = 0x01
|
|
68
64
|
target_ip = data_content['target_ip'].encode()
|
|
69
65
|
target_port = data_content['target_port']
|
|
@@ -71,74 +67,66 @@ class NatSerialization:
|
|
|
71
67
|
magic, mode_flag, len(target_client), len(target_ip), len(source_rule_name), len(protocol),
|
|
72
68
|
target_port, uid, target_client, target_ip, source_rule_name, protocol)
|
|
73
69
|
else:
|
|
74
|
-
# Service mode: check if new format or old format for backward compatibility
|
|
75
70
|
target_service = data_content['target_service'].encode()
|
|
76
|
-
|
|
77
|
-
# Always use new format when sending (with magic number for identification)
|
|
78
|
-
magic = 0xFF # Magic number to identify new format
|
|
71
|
+
magic = 0xFF
|
|
79
72
|
mode_flag = 0x00
|
|
80
73
|
body = struct.pack(f'BBBBBB{UID_LEN}s{len(target_client)}s{len(target_service)}s{len(source_rule_name)}s{len(protocol)}s',
|
|
81
74
|
magic, mode_flag, len(target_client), len(target_service), len(source_rule_name), len(protocol),
|
|
82
75
|
uid, target_client, target_service, source_rule_name, protocol)
|
|
83
76
|
|
|
84
77
|
elif type_ == MessageTypeConstant.PUSH_CONFIG:
|
|
85
|
-
body =
|
|
78
|
+
body = json.dumps(data).encode()
|
|
86
79
|
elif type_ == MessageTypeConstant.PING:
|
|
87
|
-
body =
|
|
80
|
+
body = b''
|
|
88
81
|
elif type_ in (MessageTypeConstant.P2P_OFFER, MessageTypeConstant.P2P_ANSWER,
|
|
89
82
|
MessageTypeConstant.P2P_CANDIDATE, MessageTypeConstant.P2P_SUCCESS,
|
|
90
83
|
MessageTypeConstant.P2P_FAILED, MessageTypeConstant.P2P_PRE_CONNECT,
|
|
91
84
|
MessageTypeConstant.P2P_PEER_INFO, MessageTypeConstant.P2P_PUNCH_REQUEST):
|
|
92
|
-
# P2P messages: use JSON encoding of data content only
|
|
93
85
|
data_content = data.get('data', {})
|
|
94
86
|
body = json.dumps(data_content).encode()
|
|
95
87
|
else:
|
|
96
|
-
body =
|
|
88
|
+
body = b'error'
|
|
89
|
+
|
|
97
90
|
body_len = len(body)
|
|
98
91
|
nonce = os.urandom(5)
|
|
99
92
|
timestamp = struct.pack('I', int(time.time()))
|
|
100
|
-
# 使用 xxHash64 签名(8 字节,比 MD5 快 10 倍)
|
|
101
93
|
signature = EncryptUtils.xxhash64_hash(nonce + timestamp + body[:12] + key.encode())
|
|
102
94
|
header = type_.encode() + struct.pack('I', body_len) + nonce + timestamp + signature
|
|
103
|
-
b =
|
|
95
|
+
b = header + body
|
|
104
96
|
return EncryptUtils.encrypt(b, key)
|
|
105
97
|
|
|
106
98
|
@classmethod
|
|
107
99
|
def check_signature(cls, clear_text: bytes, data_len: int, key: str) -> bool:
|
|
108
100
|
nonce_and_timestamp = clear_text[5:14]
|
|
109
101
|
body = clear_text[HEADER_LEN: data_len + HEADER_LEN]
|
|
110
|
-
signature = clear_text[14:22]
|
|
102
|
+
signature = clear_text[14:22]
|
|
111
103
|
return signature == EncryptUtils.xxhash64_hash(nonce_and_timestamp + body[:12] + key.encode())
|
|
112
104
|
|
|
113
105
|
@classmethod
|
|
114
106
|
def check_nonce_and_timestamp(cls, clear_text: bytes) -> bool:
|
|
115
107
|
return True
|
|
116
|
-
# check and Anti replay attack
|
|
117
|
-
# nonce = clear_text[5:10]
|
|
118
|
-
# timestamp = struct.unpack('I', clear_text[10:14])[0]
|
|
119
|
-
# nonce_to_time = ContextUtils.get_nonce_to_time()
|
|
120
|
-
# if nonce in nonce_to_time :
|
|
121
|
-
# return False
|
|
122
|
-
# nonce_to_time[nonce] = int(time.time())
|
|
123
|
-
# return True
|
|
124
108
|
|
|
125
109
|
@classmethod
|
|
126
110
|
def loads(cls, byte_data: bytes, key: str, compress: bool) -> MessageEntity:
|
|
127
111
|
byte_data = EncryptUtils.decrypt(byte_data, key)
|
|
128
112
|
type_ = byte_data[0:1]
|
|
129
113
|
body_len = struct.unpack('I', byte_data[1:5])[0]
|
|
130
|
-
|
|
114
|
+
|
|
131
115
|
if not cls.check_nonce_and_timestamp(byte_data):
|
|
116
|
+
from exceptions.replay_error import ReplayError
|
|
132
117
|
raise ReplayError()
|
|
133
118
|
if not cls.check_signature(byte_data, body_len, key):
|
|
119
|
+
from exceptions.signature_error import SignatureError
|
|
134
120
|
print(f'SignatureError: {key}')
|
|
135
121
|
raise SignatureError()
|
|
122
|
+
|
|
136
123
|
body = byte_data[HEADER_LEN: body_len + HEADER_LEN]
|
|
124
|
+
|
|
137
125
|
if type_.decode() in (MessageTypeConstant.WEBSOCKET_OVER_TCP, MessageTypeConstant.REQUEST_TO_CONNECT,
|
|
138
126
|
MessageTypeConstant.WEBSOCKET_OVER_UDP, MessageTypeConstant.REQUEST_TO_CONNECT_UDP,
|
|
139
|
-
MessageTypeConstant.CONNECT_CONFIRMED, MessageTypeConstant.CONNECT_FAILED):
|
|
127
|
+
MessageTypeConstant.CONNECT_CONFIRMED, MessageTypeConstant.CONNECT_FAILED):
|
|
140
128
|
len_name, len_ip_port, len_bytes = struct.unpack('BBI', body[:8])
|
|
141
|
-
uid, name, ip_port,
|
|
129
|
+
uid, name, ip_port, socket_dta = struct.unpack(f'4s{len_name}s{len_ip_port}s{len_bytes}s', body[8:])
|
|
142
130
|
if compress and len(socket_dta):
|
|
143
131
|
socket_dta = snappy.snappy.uncompress(socket_dta)
|
|
144
132
|
data: TcpOverWebsocketMessage = {
|
|
@@ -152,16 +140,14 @@ class NatSerialization:
|
|
|
152
140
|
'data': data
|
|
153
141
|
}
|
|
154
142
|
return return_data
|
|
143
|
+
|
|
155
144
|
elif type_.decode() == MessageTypeConstant.CLIENT_TO_CLIENT_FORWARD:
|
|
156
|
-
# Parse C2C forward request: support old and new formats
|
|
157
145
|
first_byte = struct.unpack('B', body[:1])[0]
|
|
158
146
|
|
|
159
147
|
if first_byte == 0xFF:
|
|
160
|
-
# New format: magic(1) | mode_flag(1) | ...
|
|
161
148
|
mode_flag = struct.unpack('B', body[1:2])[0]
|
|
162
149
|
|
|
163
150
|
if mode_flag == 0x01:
|
|
164
|
-
# Direct mode: parse target_ip and target_port
|
|
165
151
|
len_target_client, len_target_ip, len_source_rule_name, len_protocol = struct.unpack('BBBB', body[2:6])
|
|
166
152
|
target_port = struct.unpack('H', body[6:8])[0]
|
|
167
153
|
uid, target_client, target_ip, source_rule_name, protocol = struct.unpack(
|
|
@@ -175,7 +161,6 @@ class NatSerialization:
|
|
|
175
161
|
'protocol': protocol.decode()
|
|
176
162
|
}
|
|
177
163
|
else:
|
|
178
|
-
# Service mode (new format): parse target_service
|
|
179
164
|
len_target_client, len_target_service, len_source_rule_name, len_protocol = struct.unpack('BBBB', body[2:6])
|
|
180
165
|
uid, target_client, target_service, source_rule_name, protocol = struct.unpack(
|
|
181
166
|
f'4s{len_target_client}s{len_target_service}s{len_source_rule_name}s{len_protocol}s', body[6:])
|
|
@@ -187,7 +172,6 @@ class NatSerialization:
|
|
|
187
172
|
'protocol': protocol.decode()
|
|
188
173
|
}
|
|
189
174
|
else:
|
|
190
|
-
# Old format (backward compatible): len_target_client(1) | len_target_service(1) | len_source_rule_name(1) | len_protocol(1) | uid(4) | strings...
|
|
191
175
|
len_target_client = first_byte
|
|
192
176
|
len_target_service, len_source_rule_name, len_protocol = struct.unpack('BBB', body[1:4])
|
|
193
177
|
uid, target_client, target_service, source_rule_name, protocol = struct.unpack(
|
|
@@ -205,20 +189,22 @@ class NatSerialization:
|
|
|
205
189
|
'data': data
|
|
206
190
|
}
|
|
207
191
|
return return_data
|
|
192
|
+
|
|
208
193
|
elif type_ == MessageTypeConstant.PUSH_CONFIG.encode():
|
|
209
194
|
return_data: MessageEntity = json.loads(body.decode())
|
|
210
195
|
return return_data
|
|
196
|
+
|
|
211
197
|
elif type_ == MessageTypeConstant.PING.encode():
|
|
212
198
|
return_data: MessageEntity = {
|
|
213
199
|
'type_': type_.decode(),
|
|
214
200
|
'data': None
|
|
215
201
|
}
|
|
216
202
|
return return_data
|
|
203
|
+
|
|
217
204
|
elif type_.decode() in (MessageTypeConstant.P2P_OFFER, MessageTypeConstant.P2P_ANSWER,
|
|
218
205
|
MessageTypeConstant.P2P_CANDIDATE, MessageTypeConstant.P2P_SUCCESS,
|
|
219
206
|
MessageTypeConstant.P2P_FAILED, MessageTypeConstant.P2P_PRE_CONNECT,
|
|
220
207
|
MessageTypeConstant.P2P_PEER_INFO, MessageTypeConstant.P2P_PUNCH_REQUEST):
|
|
221
|
-
# P2P messages: JSON decode
|
|
222
208
|
data = json.loads(body.decode())
|
|
223
209
|
return_data: MessageEntity = {
|
|
224
210
|
'type_': type_.decode(),
|
|
@@ -226,30 +212,4 @@ class NatSerialization:
|
|
|
226
212
|
}
|
|
227
213
|
return return_data
|
|
228
214
|
else:
|
|
229
|
-
raise Exception('error
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
if __name__ == '__main__':
|
|
234
|
-
|
|
235
|
-
ContextUtils.set_nonce_to_time({})
|
|
236
|
-
def _print_commend(msg):
|
|
237
|
-
print(''.join("b'{}'".format(''.join('\\x{:02x}'.format(b) for b in msg))))
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
data = {'type_': 'a',
|
|
241
|
-
'data': {'name': 'ssh',
|
|
242
|
-
'target_client': 'abc',
|
|
243
|
-
'target_service': 'abc',
|
|
244
|
-
'source_rule_name': 'source_rule_name',
|
|
245
|
-
'protocol': 'tcp',
|
|
246
|
-
# 'data': 'SSH-2.0-OpenSSH_7.8'.encode() ,
|
|
247
|
-
'data': b'' ,
|
|
248
|
-
'uid': os.urandom(4),
|
|
249
|
-
'ip_port': '127.0.0.1:8888'}}
|
|
250
|
-
key32 = 'xxxx'
|
|
251
|
-
# salt = '!%F=-?Pst970'
|
|
252
|
-
a = NatSerialization.dumps(data, key32, False)
|
|
253
|
-
print(a)
|
|
254
|
-
b = NatSerialization.loads(a, key32, True)
|
|
255
|
-
print(b)
|
|
215
|
+
raise Exception('error')
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""
|
|
2
|
+
NatSerialization V2 - msgpack format (flexible, supports arbitrary fields)
|
|
3
|
+
"""
|
|
4
|
+
import os
|
|
5
|
+
import struct
|
|
6
|
+
import time
|
|
7
|
+
|
|
8
|
+
import msgpack
|
|
9
|
+
|
|
10
|
+
try:
|
|
11
|
+
import snappy
|
|
12
|
+
has_snappy = True
|
|
13
|
+
except ModuleNotFoundError:
|
|
14
|
+
has_snappy = False
|
|
15
|
+
|
|
16
|
+
from common.encrypt_utils import EncryptUtils
|
|
17
|
+
from entity.message.message_entity import MessageEntity
|
|
18
|
+
|
|
19
|
+
HEADER_LEN = 22 # xxHash64 模式: 22 字节
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class NatSerializationV2:
|
|
23
|
+
"""
|
|
24
|
+
V2 序列化格式 - msgpack 格式
|
|
25
|
+
|
|
26
|
+
header + body
|
|
27
|
+
|
|
28
|
+
header (22 字节) - 使用 xxHash64 签名:
|
|
29
|
+
类型(1字节) | body长度(4字节) | 随机字符串(5字节) | 时间戳(4字节) | 签名 (8 字节)
|
|
30
|
+
|
|
31
|
+
body (长度不固定):
|
|
32
|
+
msgpack 编码的数据,自动支持所有字段
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
@classmethod
|
|
36
|
+
def dumps(cls, data: MessageEntity, key: str, compress: bool) -> bytes:
|
|
37
|
+
type_ = data['type_']
|
|
38
|
+
data_content = data.get('data')
|
|
39
|
+
|
|
40
|
+
# 处理 data_content,确保可以被 msgpack 序列化
|
|
41
|
+
if data_content is not None:
|
|
42
|
+
# 复制一份避免修改原数据
|
|
43
|
+
# serializable_content = dict(data_content) if isinstance(data_content, dict) else data_content
|
|
44
|
+
serializable_content = data_content
|
|
45
|
+
|
|
46
|
+
# 如果有 'data' 字段且需要压缩
|
|
47
|
+
if isinstance(serializable_content, dict) and 'data' in serializable_content:
|
|
48
|
+
if compress and has_snappy and serializable_content['data']:
|
|
49
|
+
serializable_content['data'] = snappy.snappy.compress(serializable_content['data'])
|
|
50
|
+
serializable_content['_compressed'] = True
|
|
51
|
+
|
|
52
|
+
body = msgpack.packb(serializable_content, use_bin_type=True)
|
|
53
|
+
else:
|
|
54
|
+
body = b''
|
|
55
|
+
|
|
56
|
+
body_len = len(body)
|
|
57
|
+
nonce = os.urandom(5)
|
|
58
|
+
timestamp = struct.pack('I', int(time.time()))
|
|
59
|
+
signature = EncryptUtils.xxhash64_hash(nonce + timestamp + body[:12] + key.encode())
|
|
60
|
+
header = type_.encode() + struct.pack('I', body_len) + nonce + timestamp + signature
|
|
61
|
+
b = header + body
|
|
62
|
+
return EncryptUtils.encrypt(b, key)
|
|
63
|
+
|
|
64
|
+
@classmethod
|
|
65
|
+
def check_signature(cls, clear_text: bytes, data_len: int, key: str) -> bool:
|
|
66
|
+
nonce_and_timestamp = clear_text[5:14]
|
|
67
|
+
body = clear_text[HEADER_LEN: data_len + HEADER_LEN]
|
|
68
|
+
signature = clear_text[14:22]
|
|
69
|
+
return signature == EncryptUtils.xxhash64_hash(nonce_and_timestamp + body[:12] + key.encode())
|
|
70
|
+
|
|
71
|
+
@classmethod
|
|
72
|
+
def check_nonce_and_timestamp(cls, clear_text: bytes) -> bool:
|
|
73
|
+
return True
|
|
74
|
+
|
|
75
|
+
@classmethod
|
|
76
|
+
def loads(cls, byte_data: bytes, key: str, compress: bool) -> MessageEntity:
|
|
77
|
+
byte_data = EncryptUtils.decrypt(byte_data, key)
|
|
78
|
+
type_ = byte_data[0:1]
|
|
79
|
+
body_len = struct.unpack('I', byte_data[1:5])[0]
|
|
80
|
+
|
|
81
|
+
if not cls.check_nonce_and_timestamp(byte_data):
|
|
82
|
+
from exceptions.replay_error import ReplayError
|
|
83
|
+
raise ReplayError()
|
|
84
|
+
if not cls.check_signature(byte_data, body_len, key):
|
|
85
|
+
from exceptions.signature_error import SignatureError
|
|
86
|
+
print(f'SignatureError: {key}')
|
|
87
|
+
raise SignatureError()
|
|
88
|
+
|
|
89
|
+
body = byte_data[HEADER_LEN: body_len + HEADER_LEN]
|
|
90
|
+
|
|
91
|
+
if body:
|
|
92
|
+
data_content = msgpack.unpackb(body, raw=False)
|
|
93
|
+
|
|
94
|
+
# 如果数据被压缩过,解压
|
|
95
|
+
if isinstance(data_content, dict):
|
|
96
|
+
if data_content.get('_compressed') and 'data' in data_content:
|
|
97
|
+
if has_snappy and data_content['data']:
|
|
98
|
+
data_content['data'] = snappy.snappy.uncompress(data_content['data'])
|
|
99
|
+
del data_content['_compressed']
|
|
100
|
+
else:
|
|
101
|
+
data_content = None
|
|
102
|
+
|
|
103
|
+
return_data: MessageEntity = {
|
|
104
|
+
'type_': type_.decode(),
|
|
105
|
+
'data': data_content
|
|
106
|
+
}
|
|
107
|
+
return return_data
|
|
@@ -35,3 +35,4 @@ class PushConfigEntity(TypedDict, total=False):
|
|
|
35
35
|
p2p_supported: bool # Whether client supports P2P (optional)
|
|
36
36
|
public_ip: str # Client's public IP (filled by server, optional)
|
|
37
37
|
public_port: int # Client's public port (filled by server, optional)
|
|
38
|
+
protocol_version: int # Serialization protocol version (1=binary, 2=msgpack)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from typing_extensions import TypedDict
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class RuleEntity(TypedDict):
|
|
5
|
+
# {
|
|
6
|
+
# "name": "xjy_test",
|
|
7
|
+
# "source_client": "家里小主机",
|
|
8
|
+
# "target_client": "xinjiaya_server_test",
|
|
9
|
+
# "local_port": 33333,
|
|
10
|
+
# "local_ip": "127.0.0.1",
|
|
11
|
+
# "protocol": "tcp",
|
|
12
|
+
# "speed_limit": 0.0,
|
|
13
|
+
# "enabled": true,
|
|
14
|
+
# "p2p_enabled": false,
|
|
15
|
+
# "target_ip": "127.0.0.1",
|
|
16
|
+
# "target_port": 22
|
|
17
|
+
# }
|
|
18
|
+
name: str
|
|
19
|
+
source_client: str
|
|
20
|
+
target_client: str
|
|
21
|
+
local_port: int
|
|
22
|
+
local_ip: str
|
|
23
|
+
protocol: str
|
|
24
|
+
speed_limit: float
|
|
25
|
+
enabled: bool
|
|
26
|
+
p2p_enabled: bool
|
|
27
|
+
target_ip: str
|
|
28
|
+
target_port: int
|