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.
Files changed (103) hide show
  1. {proxynt-2.0.40 → proxynt-2.0.44}/PKG-INFO +1 -1
  2. {proxynt-2.0.40 → proxynt-2.0.44}/client/heart_beat_task.py +3 -2
  3. {proxynt-2.0.40 → proxynt-2.0.44}/client/tcp_forward_client.py +7 -6
  4. {proxynt-2.0.40 → proxynt-2.0.44}/client/udp_forward_client.py +5 -4
  5. proxynt-2.0.44/common/nat_serialization.py +129 -0
  6. proxynt-2.0.40/common/nat_serialization.py → proxynt-2.0.44/common/nat_serialization_v1.py +35 -75
  7. proxynt-2.0.44/common/nat_serialization_v2.py +107 -0
  8. {proxynt-2.0.40 → proxynt-2.0.44}/constant/system_constant.py +1 -1
  9. {proxynt-2.0.40 → proxynt-2.0.44}/entity/message/push_config_entity.py +1 -0
  10. proxynt-2.0.44/entity/rule_entity.py +28 -0
  11. {proxynt-2.0.40 → proxynt-2.0.44}/proxynt.egg-info/PKG-INFO +1 -1
  12. {proxynt-2.0.40 → proxynt-2.0.44}/proxynt.egg-info/SOURCES.txt +3 -0
  13. {proxynt-2.0.40 → proxynt-2.0.44}/proxynt.egg-info/requires.txt +1 -0
  14. {proxynt-2.0.40 → proxynt-2.0.44}/run_client.py +17 -6
  15. {proxynt-2.0.40 → proxynt-2.0.44}/server/admin_http_handler.py +2 -1
  16. {proxynt-2.0.40 → proxynt-2.0.44}/server/n4_signal_service.py +2 -2
  17. {proxynt-2.0.40 → proxynt-2.0.44}/server/task/heart_beat_task.py +1 -1
  18. {proxynt-2.0.40 → proxynt-2.0.44}/server/tcp_forward_client.py +6 -3
  19. {proxynt-2.0.40 → proxynt-2.0.44}/server/template/ele_index.html +9 -0
  20. {proxynt-2.0.40 → proxynt-2.0.44}/server/udp_forward_client.py +4 -2
  21. {proxynt-2.0.40 → proxynt-2.0.44}/server/websocket_handler.py +26 -18
  22. {proxynt-2.0.40 → proxynt-2.0.44}/setup.py +1 -0
  23. {proxynt-2.0.40 → proxynt-2.0.44}/LICENSE +0 -0
  24. {proxynt-2.0.40 → proxynt-2.0.44}/MANIFEST.in +0 -0
  25. {proxynt-2.0.40 → proxynt-2.0.44}/__init__.py +0 -0
  26. {proxynt-2.0.40 → proxynt-2.0.44}/client/__init__.py +0 -0
  27. {proxynt-2.0.40 → proxynt-2.0.44}/client/abstract_tunnel.py +0 -0
  28. {proxynt-2.0.40 → proxynt-2.0.44}/client/clear_nonce_task.py +0 -0
  29. {proxynt-2.0.40 → proxynt-2.0.44}/client/kcp_tunnel_impl.py +0 -0
  30. {proxynt-2.0.40 → proxynt-2.0.44}/client/n4_tunnel_manager.py +0 -0
  31. {proxynt-2.0.40 → proxynt-2.0.44}/client/quic_tunnel_impl.py +0 -0
  32. {proxynt-2.0.40 → proxynt-2.0.44}/client/tunnel_protocol.py +0 -0
  33. {proxynt-2.0.40 → proxynt-2.0.44}/common/__init__.py +0 -0
  34. {proxynt-2.0.40 → proxynt-2.0.44}/common/cert_utils.py +0 -0
  35. {proxynt-2.0.40 → proxynt-2.0.44}/common/crypto/__init__.py +0 -0
  36. {proxynt-2.0.40 → proxynt-2.0.44}/common/crypto/table.py +0 -0
  37. {proxynt-2.0.40 → proxynt-2.0.44}/common/encrypt_utils.py +0 -0
  38. {proxynt-2.0.40 → proxynt-2.0.44}/common/kcp.py +0 -0
  39. {proxynt-2.0.40 → proxynt-2.0.44}/common/logger_factory.py +0 -0
  40. {proxynt-2.0.40 → proxynt-2.0.44}/common/n4_protocol.py +0 -0
  41. {proxynt-2.0.40 → proxynt-2.0.44}/common/n4_punch.py +0 -0
  42. {proxynt-2.0.40 → proxynt-2.0.44}/common/pool.py +0 -0
  43. {proxynt-2.0.40 → proxynt-2.0.44}/common/register_append_data.py +0 -0
  44. {proxynt-2.0.40 → proxynt-2.0.44}/common/speed_limit.py +0 -0
  45. {proxynt-2.0.40 → proxynt-2.0.44}/common/websocket/__init__.py +0 -0
  46. {proxynt-2.0.40 → proxynt-2.0.44}/common/websocket/_abnf.py +0 -0
  47. {proxynt-2.0.40 → proxynt-2.0.44}/common/websocket/_app.py +0 -0
  48. {proxynt-2.0.40 → proxynt-2.0.44}/common/websocket/_cookiejar.py +0 -0
  49. {proxynt-2.0.40 → proxynt-2.0.44}/common/websocket/_core.py +0 -0
  50. {proxynt-2.0.40 → proxynt-2.0.44}/common/websocket/_exceptions.py +0 -0
  51. {proxynt-2.0.40 → proxynt-2.0.44}/common/websocket/_handshake.py +0 -0
  52. {proxynt-2.0.40 → proxynt-2.0.44}/common/websocket/_http.py +0 -0
  53. {proxynt-2.0.40 → proxynt-2.0.44}/common/websocket/_logging.py +0 -0
  54. {proxynt-2.0.40 → proxynt-2.0.44}/common/websocket/_socket.py +0 -0
  55. {proxynt-2.0.40 → proxynt-2.0.44}/common/websocket/_ssl_compat.py +0 -0
  56. {proxynt-2.0.40 → proxynt-2.0.44}/common/websocket/_url.py +0 -0
  57. {proxynt-2.0.40 → proxynt-2.0.44}/common/websocket/_utils.py +0 -0
  58. {proxynt-2.0.40 → proxynt-2.0.44}/common/websocket/_wsdump.py +0 -0
  59. {proxynt-2.0.40 → proxynt-2.0.44}/common/websocket/tests/__init__.py +0 -0
  60. {proxynt-2.0.40 → proxynt-2.0.44}/common/websocket/tests/echo-server.py +0 -0
  61. {proxynt-2.0.40 → proxynt-2.0.44}/common/websocket/tests/test_abnf.py +0 -0
  62. {proxynt-2.0.40 → proxynt-2.0.44}/common/websocket/tests/test_app.py +0 -0
  63. {proxynt-2.0.40 → proxynt-2.0.44}/common/websocket/tests/test_cookiejar.py +0 -0
  64. {proxynt-2.0.40 → proxynt-2.0.44}/common/websocket/tests/test_http.py +0 -0
  65. {proxynt-2.0.40 → proxynt-2.0.44}/common/websocket/tests/test_url.py +0 -0
  66. {proxynt-2.0.40 → proxynt-2.0.44}/common/websocket/tests/test_websocket.py +0 -0
  67. {proxynt-2.0.40 → proxynt-2.0.44}/constant/__init__.py +0 -0
  68. {proxynt-2.0.40 → proxynt-2.0.44}/constant/message_type_constnat.py +0 -0
  69. {proxynt-2.0.40 → proxynt-2.0.44}/context/__init__.py +0 -0
  70. {proxynt-2.0.40 → proxynt-2.0.44}/context/context_utils.py +0 -0
  71. {proxynt-2.0.40 → proxynt-2.0.44}/entity/__init__.py +0 -0
  72. {proxynt-2.0.40 → proxynt-2.0.44}/entity/client_config_entity.py +0 -0
  73. {proxynt-2.0.40 → proxynt-2.0.44}/entity/message/__init__.py +0 -0
  74. {proxynt-2.0.40 → proxynt-2.0.44}/entity/message/message_entity.py +0 -0
  75. {proxynt-2.0.40 → proxynt-2.0.44}/entity/message/tcp_over_websocket_message.py +0 -0
  76. {proxynt-2.0.40 → proxynt-2.0.44}/entity/server_config_entity.py +0 -0
  77. {proxynt-2.0.40 → proxynt-2.0.44}/exceptions/__init__.py +0 -0
  78. {proxynt-2.0.40 → proxynt-2.0.44}/exceptions/duplicated_name.py +0 -0
  79. {proxynt-2.0.40 → proxynt-2.0.44}/exceptions/invalid_password.py +0 -0
  80. {proxynt-2.0.40 → proxynt-2.0.44}/exceptions/replay_error.py +0 -0
  81. {proxynt-2.0.40 → proxynt-2.0.44}/exceptions/signature_error.py +0 -0
  82. {proxynt-2.0.40 → proxynt-2.0.44}/p2ptest/__init__.py +0 -0
  83. {proxynt-2.0.40 → proxynt-2.0.44}/p2ptest/client.py +0 -0
  84. {proxynt-2.0.40 → proxynt-2.0.44}/p2ptest/n4.py +0 -0
  85. {proxynt-2.0.40 → proxynt-2.0.44}/proxynt.egg-info/dependency_links.txt +0 -0
  86. {proxynt-2.0.40 → proxynt-2.0.44}/proxynt.egg-info/entry_points.txt +0 -0
  87. {proxynt-2.0.40 → proxynt-2.0.44}/proxynt.egg-info/top_level.txt +0 -0
  88. {proxynt-2.0.40 → proxynt-2.0.44}/run_server.py +0 -0
  89. {proxynt-2.0.40 → proxynt-2.0.44}/server/__init__.py +0 -0
  90. {proxynt-2.0.40 → proxynt-2.0.44}/server/n4.py +0 -0
  91. {proxynt-2.0.40 → proxynt-2.0.44}/server/task/__init__.py +0 -0
  92. {proxynt-2.0.40 → proxynt-2.0.44}/server/task/check_cookie_task.py +0 -0
  93. {proxynt-2.0.40 → proxynt-2.0.44}/server/task/clear_nonce_task.py +0 -0
  94. {proxynt-2.0.40 → proxynt-2.0.44}/server/template/__init__.py +0 -0
  95. {proxynt-2.0.40 → proxynt-2.0.44}/server/template/base.html +0 -0
  96. {proxynt-2.0.40 → proxynt-2.0.44}/server/template/css/fonts/element-icons.woff +0 -0
  97. {proxynt-2.0.40 → proxynt-2.0.44}/server/template/css/index.css +0 -0
  98. {proxynt-2.0.40 → proxynt-2.0.44}/server/template/js/axios.min.js +0 -0
  99. {proxynt-2.0.40 → proxynt-2.0.44}/server/template/js/index.js +0 -0
  100. {proxynt-2.0.40 → proxynt-2.0.44}/server/template/js/vue.min.js +0 -0
  101. {proxynt-2.0.40 → proxynt-2.0.44}/server/template/login.html +0 -0
  102. {proxynt-2.0.40 → proxynt-2.0.44}/setup.cfg +0 -0
  103. {proxynt-2.0.40 → proxynt-2.0.44}/test_exchange.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: proxynt
3
- Version: 2.0.40
3
+ Version: 2.0.44
4
4
  Summary: UNKNOWN
5
5
  Home-page: https://github.com/sazima/proxynt
6
6
  License: UNKNOWN
@@ -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 字节(比 MD5 模式节省 42%)
21
+ HEADER_LEN = 22 # xxHash64 模式: 22 字节
23
22
 
24
23
 
25
- class NatSerialization:
24
+ class NatSerializationV1:
26
25
  """
27
- header + body
26
+ V1 序列化格式 - 二进制格式(原始实现)
28
27
 
29
- header (22 字节) - 使用 xxHash64 签名:
30
- 类型(1字节) | body长度(4字节) | 随机字符串(5字节) | 时间戳(4字节) | 签名 (8 字节)
28
+ header + body
31
29
 
32
- body (长度不固定):
33
- 实际数据
30
+ header (22 字节) - 使用 xxHash64 签名:
31
+ 类型(1字节) | body长度(4字节) | 随机字符串(5字节) | 时间戳(4字节) | 签名 (8 字节)
34
32
 
35
- 注:使用 xxHash64 替代 MD5,签名速度提升 10 倍,头部大小减少 42%
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'] # 长度r
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', len(name.encode()), len(ip_port), len(bytes_), uid, name.encode(), ip_port.encode(), bytes_)
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
- # Direct mode: magic(1) | mode_flag(1) | len_target_client(1) | len_target_ip(1) | len_source_rule_name(1) | len_protocol(1) | target_port(2) | uid(4) | strings...
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 = json.dumps(data).encode()
78
+ body = json.dumps(data).encode()
86
79
  elif type_ == MessageTypeConstant.PING:
87
- body = b''
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 = b'error'
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 = header + body
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] # xxHash64 签名 8 字节
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
- header = byte_data[:HEADER_LEN]
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, socket_dta = struct.unpack(f'4s{len_name}s{len_ip_port}s{len_bytes}s', body[8:])
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
@@ -15,7 +15,7 @@ class SystemConstant:
15
15
 
16
16
  COOKIE_EXPIRE_SECONDS = 3600 * 24
17
17
 
18
- VERSION = '2.0.40'
18
+ VERSION = '2.0.44'
19
19
 
20
20
  GITHUB = 'https://github.com/sazima/proxynt'
21
21
 
@@ -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