proxynt 2.0.3__tar.gz → 2.0.8__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 (95) hide show
  1. {proxynt-2.0.3/proxynt.egg-info → proxynt-2.0.8}/PKG-INFO +1 -1
  2. proxynt-2.0.8/client/p2p_hole_punch.py +257 -0
  3. {proxynt-2.0.3 → proxynt-2.0.8}/client/tcp_forward_client.py +6 -0
  4. {proxynt-2.0.3 → proxynt-2.0.8}/common/nat_serialization.py +16 -0
  5. {proxynt-2.0.3 → proxynt-2.0.8}/constant/message_type_constnat.py +7 -0
  6. {proxynt-2.0.3 → proxynt-2.0.8}/constant/system_constant.py +6 -1
  7. {proxynt-2.0.3 → proxynt-2.0.8}/entity/message/push_config_entity.py +8 -4
  8. {proxynt-2.0.3 → proxynt-2.0.8}/entity/server_config_entity.py +1 -0
  9. {proxynt-2.0.3 → proxynt-2.0.8/proxynt.egg-info}/PKG-INFO +1 -1
  10. proxynt-2.0.8/proxynt.egg-info/SOURCES.txt +84 -0
  11. {proxynt-2.0.3 → proxynt-2.0.8}/run_client.py +177 -3
  12. {proxynt-2.0.3 → proxynt-2.0.8}/server/admin_http_handler.py +3 -1
  13. {proxynt-2.0.3 → proxynt-2.0.8}/server/task/heart_beat_task.py +4 -2
  14. {proxynt-2.0.3/build/lib/proxynt → proxynt-2.0.8}/server/template/ele_index.html +14 -4
  15. {proxynt-2.0.3 → proxynt-2.0.8}/server/websocket_handler.py +159 -48
  16. proxynt-2.0.3/proxynt.egg-info/SOURCES.txt +0 -165
  17. proxynt-2.0.3/server/template/base.html +0 -38
  18. proxynt-2.0.3/server/template/css/fonts/element-icons.woff +0 -0
  19. proxynt-2.0.3/server/template/css/index.css +0 -1
  20. proxynt-2.0.3/server/template/ele_index.html +0 -605
  21. proxynt-2.0.3/server/template/js/axios.min.js +0 -3
  22. proxynt-2.0.3/server/template/js/index.js +0 -1
  23. proxynt-2.0.3/server/template/js/vue.min.js +0 -6
  24. proxynt-2.0.3/server/template/login.html +0 -54
  25. {proxynt-2.0.3 → proxynt-2.0.8}/LICENSE +0 -0
  26. {proxynt-2.0.3 → proxynt-2.0.8}/MANIFEST.in +0 -0
  27. {proxynt-2.0.3 → proxynt-2.0.8}/__init__.py +0 -0
  28. {proxynt-2.0.3 → proxynt-2.0.8}/client/__init__.py +0 -0
  29. {proxynt-2.0.3 → proxynt-2.0.8}/client/clear_nonce_task.py +0 -0
  30. {proxynt-2.0.3 → proxynt-2.0.8}/client/heart_beat_task.py +0 -0
  31. {proxynt-2.0.3 → proxynt-2.0.8}/client/udp_forward_client.py +0 -0
  32. {proxynt-2.0.3 → proxynt-2.0.8}/common/__init__.py +0 -0
  33. {proxynt-2.0.3 → proxynt-2.0.8}/common/crypto/__init__.py +0 -0
  34. {proxynt-2.0.3 → proxynt-2.0.8}/common/crypto/table.py +0 -0
  35. {proxynt-2.0.3 → proxynt-2.0.8}/common/encrypt_utils.py +0 -0
  36. {proxynt-2.0.3 → proxynt-2.0.8}/common/logger_factory.py +0 -0
  37. {proxynt-2.0.3 → proxynt-2.0.8}/common/pool.py +0 -0
  38. {proxynt-2.0.3 → proxynt-2.0.8}/common/register_append_data.py +0 -0
  39. {proxynt-2.0.3 → proxynt-2.0.8}/common/speed_limit.py +0 -0
  40. {proxynt-2.0.3 → proxynt-2.0.8}/common/websocket/__init__.py +0 -0
  41. {proxynt-2.0.3 → proxynt-2.0.8}/common/websocket/_abnf.py +0 -0
  42. {proxynt-2.0.3 → proxynt-2.0.8}/common/websocket/_app.py +0 -0
  43. {proxynt-2.0.3 → proxynt-2.0.8}/common/websocket/_cookiejar.py +0 -0
  44. {proxynt-2.0.3 → proxynt-2.0.8}/common/websocket/_core.py +0 -0
  45. {proxynt-2.0.3 → proxynt-2.0.8}/common/websocket/_exceptions.py +0 -0
  46. {proxynt-2.0.3 → proxynt-2.0.8}/common/websocket/_handshake.py +0 -0
  47. {proxynt-2.0.3 → proxynt-2.0.8}/common/websocket/_http.py +0 -0
  48. {proxynt-2.0.3 → proxynt-2.0.8}/common/websocket/_logging.py +0 -0
  49. {proxynt-2.0.3 → proxynt-2.0.8}/common/websocket/_socket.py +0 -0
  50. {proxynt-2.0.3 → proxynt-2.0.8}/common/websocket/_ssl_compat.py +0 -0
  51. {proxynt-2.0.3 → proxynt-2.0.8}/common/websocket/_url.py +0 -0
  52. {proxynt-2.0.3 → proxynt-2.0.8}/common/websocket/_utils.py +0 -0
  53. {proxynt-2.0.3 → proxynt-2.0.8}/common/websocket/_wsdump.py +0 -0
  54. {proxynt-2.0.3 → proxynt-2.0.8}/common/websocket/tests/__init__.py +0 -0
  55. {proxynt-2.0.3 → proxynt-2.0.8}/common/websocket/tests/echo-server.py +0 -0
  56. {proxynt-2.0.3 → proxynt-2.0.8}/common/websocket/tests/test_abnf.py +0 -0
  57. {proxynt-2.0.3 → proxynt-2.0.8}/common/websocket/tests/test_app.py +0 -0
  58. {proxynt-2.0.3 → proxynt-2.0.8}/common/websocket/tests/test_cookiejar.py +0 -0
  59. {proxynt-2.0.3 → proxynt-2.0.8}/common/websocket/tests/test_http.py +0 -0
  60. {proxynt-2.0.3 → proxynt-2.0.8}/common/websocket/tests/test_url.py +0 -0
  61. {proxynt-2.0.3 → proxynt-2.0.8}/common/websocket/tests/test_websocket.py +0 -0
  62. {proxynt-2.0.3 → proxynt-2.0.8}/constant/__init__.py +0 -0
  63. {proxynt-2.0.3 → proxynt-2.0.8}/context/__init__.py +0 -0
  64. {proxynt-2.0.3 → proxynt-2.0.8}/context/context_utils.py +0 -0
  65. {proxynt-2.0.3 → proxynt-2.0.8}/entity/__init__.py +0 -0
  66. {proxynt-2.0.3 → proxynt-2.0.8}/entity/client_config_entity.py +0 -0
  67. {proxynt-2.0.3 → proxynt-2.0.8}/entity/message/__init__.py +0 -0
  68. {proxynt-2.0.3 → proxynt-2.0.8}/entity/message/message_entity.py +0 -0
  69. {proxynt-2.0.3 → proxynt-2.0.8}/entity/message/tcp_over_websocket_message.py +0 -0
  70. {proxynt-2.0.3 → proxynt-2.0.8}/exceptions/__init__.py +0 -0
  71. {proxynt-2.0.3 → proxynt-2.0.8}/exceptions/duplicated_name.py +0 -0
  72. {proxynt-2.0.3 → proxynt-2.0.8}/exceptions/invalid_password.py +0 -0
  73. {proxynt-2.0.3 → proxynt-2.0.8}/exceptions/replay_error.py +0 -0
  74. {proxynt-2.0.3 → proxynt-2.0.8}/exceptions/signature_error.py +0 -0
  75. {proxynt-2.0.3 → proxynt-2.0.8}/proxynt.egg-info/dependency_links.txt +0 -0
  76. {proxynt-2.0.3 → proxynt-2.0.8}/proxynt.egg-info/entry_points.txt +0 -0
  77. {proxynt-2.0.3 → proxynt-2.0.8}/proxynt.egg-info/requires.txt +0 -0
  78. {proxynt-2.0.3 → proxynt-2.0.8}/proxynt.egg-info/top_level.txt +0 -0
  79. {proxynt-2.0.3 → proxynt-2.0.8}/run_server.py +0 -0
  80. {proxynt-2.0.3 → proxynt-2.0.8}/server/__init__.py +0 -0
  81. {proxynt-2.0.3 → proxynt-2.0.8}/server/task/__init__.py +0 -0
  82. {proxynt-2.0.3 → proxynt-2.0.8}/server/task/check_cookie_task.py +0 -0
  83. {proxynt-2.0.3 → proxynt-2.0.8}/server/task/clear_nonce_task.py +0 -0
  84. {proxynt-2.0.3 → proxynt-2.0.8}/server/tcp_forward_client.py +0 -0
  85. {proxynt-2.0.3 → proxynt-2.0.8}/server/template/__init__.py +0 -0
  86. {proxynt-2.0.3/build/lib/proxynt → proxynt-2.0.8}/server/template/base.html +0 -0
  87. {proxynt-2.0.3/build/lib/proxynt → proxynt-2.0.8}/server/template/css/fonts/element-icons.woff +0 -0
  88. {proxynt-2.0.3/build/lib/proxynt → proxynt-2.0.8}/server/template/css/index.css +0 -0
  89. {proxynt-2.0.3/build/lib/proxynt → proxynt-2.0.8}/server/template/js/axios.min.js +0 -0
  90. {proxynt-2.0.3/build/lib/proxynt → proxynt-2.0.8}/server/template/js/index.js +0 -0
  91. {proxynt-2.0.3/build/lib/proxynt → proxynt-2.0.8}/server/template/js/vue.min.js +0 -0
  92. {proxynt-2.0.3/build/lib/proxynt → proxynt-2.0.8}/server/template/login.html +0 -0
  93. {proxynt-2.0.3 → proxynt-2.0.8}/server/udp_forward_client.py +0 -0
  94. {proxynt-2.0.3 → proxynt-2.0.8}/setup.cfg +0 -0
  95. {proxynt-2.0.3 → proxynt-2.0.8}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: proxynt
3
- Version: 2.0.3
3
+ Version: 2.0.8
4
4
  Summary: UNKNOWN
5
5
  Home-page: https://github.com/sazima/proxynt
6
6
  License: UNKNOWN
@@ -0,0 +1,257 @@
1
+ """
2
+ UDP Hole Punching Module for P2P Connection
3
+ """
4
+ import os
5
+ import socket
6
+ import threading
7
+ import time
8
+ import traceback
9
+ from typing import Dict, Optional, Callable
10
+
11
+ from common.logger_factory import LoggerFactory
12
+ from constant.system_constant import SystemConstant
13
+
14
+
15
+ class P2PConnection:
16
+ """Represents a P2P connection"""
17
+ def __init__(self, uid: bytes, local_socket: socket.socket, remote_addr: tuple):
18
+ self.uid = uid
19
+ self.local_socket = local_socket
20
+ self.remote_addr = remote_addr # (ip, port)
21
+ self.established = False
22
+ self.last_receive_time = 0
23
+ self.last_send_time = 0
24
+
25
+
26
+ class P2PHolePunch:
27
+ """UDP Hole Punching Manager"""
28
+
29
+ def __init__(self):
30
+ self.connections: Dict[bytes, P2PConnection] = {}
31
+ self.lock = threading.Lock()
32
+ self.running = False
33
+ self.receive_thread: Optional[threading.Thread] = None
34
+
35
+ # Callbacks
36
+ self.on_connection_established: Optional[Callable[[bytes], None]] = None
37
+ self.on_connection_failed: Optional[Callable[[bytes], None]] = None
38
+ self.on_data_received: Optional[Callable[[bytes, bytes], None]] = None
39
+
40
+ def start(self):
41
+ """Start the hole punching service"""
42
+ if self.running:
43
+ return
44
+
45
+ self.running = True
46
+ self.receive_thread = threading.Thread(target=self._receive_loop, daemon=True)
47
+ self.receive_thread.start()
48
+ LoggerFactory.get_logger().info('P2P hole punching service started')
49
+
50
+ def stop(self):
51
+ """Stop the hole punching service"""
52
+ self.running = False
53
+ with self.lock:
54
+ for uid, conn in list(self.connections.items()):
55
+ try:
56
+ conn.local_socket.close()
57
+ except Exception:
58
+ pass
59
+ self.connections.clear()
60
+ LoggerFactory.get_logger().info('P2P hole punching service stopped')
61
+
62
+ def initiate_connection(self, uid: bytes, remote_ip: str, remote_port: int, local_port: int = 0) -> bool:
63
+ """
64
+ Initiate a P2P connection
65
+
66
+ :param uid: Unique connection ID
67
+ :param remote_ip: Remote peer's public IP
68
+ :param remote_port: Remote peer's public port
69
+ :param local_port: Local port to bind (0 for random)
70
+ :return: True if initiated successfully
71
+ """
72
+ try:
73
+ # Create UDP socket
74
+ udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
75
+ udp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
76
+
77
+ # Bind to local port
78
+ if local_port > 0:
79
+ udp_socket.bind(('0.0.0.0', local_port))
80
+ else:
81
+ udp_socket.bind(('0.0.0.0', 0)) # Let OS assign port
82
+
83
+ local_addr = udp_socket.getsockname()
84
+ LoggerFactory.get_logger().info(f'P2P connection initiated: UID {uid.hex()}, local port: {local_addr[1]}, remote: {remote_ip}:{remote_port}')
85
+
86
+ # Create connection object
87
+ remote_addr = (remote_ip, remote_port)
88
+ connection = P2PConnection(uid, udp_socket, remote_addr)
89
+
90
+ with self.lock:
91
+ self.connections[uid] = connection
92
+
93
+ # Start hole punching process
94
+ self._start_hole_punching(connection)
95
+ return True
96
+
97
+ except Exception as e:
98
+ LoggerFactory.get_logger().error(f'Failed to initiate P2P connection: {e}')
99
+ LoggerFactory.get_logger().error(traceback.format_exc())
100
+ return False
101
+
102
+ def _start_hole_punching(self, connection: P2PConnection):
103
+ """
104
+ Start the hole punching process
105
+ Send periodic punch packets to remote peer
106
+ """
107
+ def punch_loop():
108
+ uid = connection.uid
109
+ sock = connection.local_socket
110
+ remote_addr = connection.remote_addr
111
+ retry_count = 0
112
+ max_retries = SystemConstant.P2P_MAX_RETRY
113
+
114
+ # Punch packet format: "PUNCH:" + UID (to distinguish from data packets)
115
+ punch_packet = b'PUNCH:' + uid
116
+
117
+ while self.running and not connection.established and retry_count < max_retries:
118
+ try:
119
+ # Send punch packet
120
+ sock.sendto(punch_packet, remote_addr)
121
+ connection.last_send_time = time.time()
122
+ LoggerFactory.get_logger().debug(f'Sent punch packet to {remote_addr}, attempt {retry_count + 1}/{max_retries}')
123
+
124
+ # Wait for response
125
+ time.sleep(1)
126
+ retry_count += 1
127
+
128
+ # Check if connection established (by receive loop)
129
+ if connection.established:
130
+ LoggerFactory.get_logger().info(f'P2P connection established: UID {uid.hex()}, remote: {remote_addr}')
131
+ if self.on_connection_established:
132
+ self.on_connection_established(uid)
133
+ return
134
+
135
+ except Exception as e:
136
+ LoggerFactory.get_logger().error(f'Error during hole punching: {e}')
137
+ break
138
+
139
+ # Timeout or failed
140
+ if not connection.established:
141
+ LoggerFactory.get_logger().warning(f'P2P hole punching failed: UID {uid.hex()}, remote: {remote_addr}')
142
+ self.close_connection(uid)
143
+ if self.on_connection_failed:
144
+ self.on_connection_failed(uid)
145
+
146
+ # Start punch thread
147
+ punch_thread = threading.Thread(target=punch_loop, daemon=True)
148
+ punch_thread.start()
149
+
150
+ def _receive_loop(self):
151
+ """Receive loop for all P2P connections"""
152
+ while self.running:
153
+ try:
154
+ # Check all connections for incoming data
155
+ with self.lock:
156
+ connections = list(self.connections.values())
157
+
158
+ for conn in connections:
159
+ try:
160
+ conn.local_socket.settimeout(0.1) # Non-blocking with short timeout
161
+ data, addr = conn.local_socket.recvfrom(65536)
162
+
163
+ if not data:
164
+ continue
165
+
166
+ # Check if this is a punch packet
167
+ if data.startswith(b'PUNCH:'):
168
+ punch_uid = data[6:10] # Extract UID from punch packet
169
+ if punch_uid == conn.uid:
170
+ # Received punch packet from remote peer
171
+ LoggerFactory.get_logger().info(f'Received punch packet from {addr} for UID {conn.uid.hex()}')
172
+
173
+ # Send punch reply
174
+ reply_packet = b'PUNCH_ACK:' + conn.uid
175
+ conn.local_socket.sendto(reply_packet, addr)
176
+
177
+ # Update remote address (might have changed due to NAT)
178
+ conn.remote_addr = addr
179
+ conn.established = True
180
+ conn.last_receive_time = time.time()
181
+
182
+ elif data.startswith(b'PUNCH_ACK:'):
183
+ # Received punch acknowledgment
184
+ LoggerFactory.get_logger().info(f'Received punch ACK from {addr} for UID {conn.uid.hex()}')
185
+ conn.remote_addr = addr
186
+ conn.established = True
187
+ conn.last_receive_time = time.time()
188
+
189
+ else:
190
+ # Regular data packet
191
+ if conn.established:
192
+ conn.last_receive_time = time.time()
193
+ if self.on_data_received:
194
+ self.on_data_received(conn.uid, data)
195
+ else:
196
+ LoggerFactory.get_logger().warning(f'Received data on unestablished connection: UID {conn.uid.hex()}')
197
+
198
+ except socket.timeout:
199
+ continue
200
+ except OSError:
201
+ # Socket might be closed
202
+ continue
203
+ except Exception as e:
204
+ LoggerFactory.get_logger().error(f'Error in receive loop: {e}')
205
+
206
+ # Small sleep to prevent CPU spinning
207
+ time.sleep(0.01)
208
+
209
+ except Exception as e:
210
+ LoggerFactory.get_logger().error(f'Fatal error in P2P receive loop: {e}')
211
+ LoggerFactory.get_logger().error(traceback.format_exc())
212
+ time.sleep(1)
213
+
214
+ def send_data(self, uid: bytes, data: bytes) -> bool:
215
+ """
216
+ Send data over P2P connection
217
+
218
+ :param uid: Connection UID
219
+ :param data: Data to send
220
+ :return: True if sent successfully
221
+ """
222
+ with self.lock:
223
+ connection = self.connections.get(uid)
224
+
225
+ if not connection:
226
+ LoggerFactory.get_logger().warning(f'P2P connection not found: UID {uid.hex()}')
227
+ return False
228
+
229
+ if not connection.established:
230
+ LoggerFactory.get_logger().warning(f'P2P connection not established: UID {uid.hex()}')
231
+ return False
232
+
233
+ try:
234
+ connection.local_socket.sendto(data, connection.remote_addr)
235
+ connection.last_send_time = time.time()
236
+ return True
237
+ except Exception as e:
238
+ LoggerFactory.get_logger().error(f'Failed to send P2P data: {e}')
239
+ return False
240
+
241
+ def is_established(self, uid: bytes) -> bool:
242
+ """Check if P2P connection is established"""
243
+ with self.lock:
244
+ connection = self.connections.get(uid)
245
+ return connection is not None and connection.established
246
+
247
+ def close_connection(self, uid: bytes):
248
+ """Close a P2P connection"""
249
+ with self.lock:
250
+ connection = self.connections.pop(uid, None)
251
+
252
+ if connection:
253
+ try:
254
+ connection.local_socket.close()
255
+ except Exception:
256
+ pass
257
+ LoggerFactory.get_logger().info(f'P2P connection closed: UID {uid.hex()}')
@@ -319,6 +319,12 @@ class TcpForwardClient:
319
319
  try:
320
320
  if LoggerFactory.get_logger().isEnabledFor(logging.DEBUG):
321
321
  LoggerFactory.get_logger().debug(f'create socket {name}, {uid}')
322
+
323
+ # Validate ip_port before splitting
324
+ if not ip_port or ':' not in ip_port:
325
+ LoggerFactory.get_logger().error(f'Invalid ip_port: {repr(ip_port)}, name: {name}, uid: {uid.hex()}')
326
+ return False
327
+
322
328
  s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
323
329
  s.settimeout(5)
324
330
  # Enable TCP_NODELAY to reduce latency (disable Nagle algorithm)
@@ -85,6 +85,12 @@ class NatSerialization:
85
85
  body = json.dumps(data).encode()
86
86
  elif type_ == MessageTypeConstant.PING:
87
87
  body = b''
88
+ elif type_ in (MessageTypeConstant.P2P_OFFER, MessageTypeConstant.P2P_ANSWER,
89
+ MessageTypeConstant.P2P_CANDIDATE, MessageTypeConstant.P2P_SUCCESS,
90
+ MessageTypeConstant.P2P_FAILED):
91
+ # P2P messages: use JSON encoding of data content only
92
+ data_content = data.get('data', {})
93
+ body = json.dumps(data_content).encode()
88
94
  else:
89
95
  body = b'error'
90
96
  body_len = len(body)
@@ -207,6 +213,16 @@ class NatSerialization:
207
213
  'data': None
208
214
  }
209
215
  return return_data
216
+ elif type_.decode() in (MessageTypeConstant.P2P_OFFER, MessageTypeConstant.P2P_ANSWER,
217
+ MessageTypeConstant.P2P_CANDIDATE, MessageTypeConstant.P2P_SUCCESS,
218
+ MessageTypeConstant.P2P_FAILED):
219
+ # P2P messages: JSON decode
220
+ data = json.loads(body.decode())
221
+ return_data: MessageEntity = {
222
+ 'type_': type_.decode(),
223
+ 'data': data
224
+ }
225
+ return return_data
210
226
  else:
211
227
  raise Exception('error ')
212
228
 
@@ -17,3 +17,10 @@ class MessageTypeConstant:
17
17
 
18
18
  # 客户端到客户端转发
19
19
  CLIENT_TO_CLIENT_FORWARD = 'a' # 客户端请求转发到另一个客户端
20
+
21
+ # P2P hole punching
22
+ P2P_OFFER = 'b' # Client A requests P2P connection with client B
23
+ P2P_ANSWER = 'c' # Client B responds to P2P request
24
+ P2P_CANDIDATE = 'd' # Exchange NAT traversal information
25
+ P2P_SUCCESS = 'e' # P2P connection established successfully
26
+ P2P_FAILED = 'f' # P2P connection failed, fallback to relay
@@ -15,6 +15,11 @@ class SystemConstant:
15
15
 
16
16
  COOKIE_EXPIRE_SECONDS = 3600 * 24
17
17
 
18
- VERSION = '2.0.3'
18
+ VERSION = '2.0.8'
19
19
 
20
20
  GITHUB = 'https://github.com/sazima/proxynt'
21
+
22
+ # P2P settings
23
+ P2P_SUPPORTED_VERSION = '2.0.3' # Minimum version that supports P2P
24
+ P2P_HOLE_PUNCH_TIMEOUT = 5 # Seconds to wait for hole punching
25
+ P2P_MAX_RETRY = 3 # Max retry attempts for hole punching
@@ -23,11 +23,15 @@ class ClientToClientRule(TypedDict, total=False):
23
23
  local_ip: str # Source client local listening IP
24
24
  protocol: str # tcp or udp
25
25
  speed_limit: float # Speed limit
26
+ p2p_enabled: bool # Whether to enable P2P for this rule (default: True)
26
27
 
27
28
 
28
- class PushConfigEntity(TypedDict):
29
+ class PushConfigEntity(TypedDict, total=False):
29
30
  key: str
30
31
  version: str
31
- config_list: List[ClientData] # 转发配置列表
32
- client_name: str # 客户端名称
33
- client_to_client_rules: List[ClientToClientRule] # C2C 转发规则(可选)
32
+ config_list: List[ClientData] # Forward configuration list
33
+ client_name: str # Client name
34
+ client_to_client_rules: List[ClientToClientRule] # C2C forward rules (optional)
35
+ p2p_supported: bool # Whether client supports P2P (optional)
36
+ public_ip: str # Client's public IP (filled by server, optional)
37
+ public_port: int # Client's public port (filled by server, optional)
@@ -21,6 +21,7 @@ class _ClientToClientRule(TypedDict, total=False):
21
21
  protocol: str
22
22
  speed_limit: float
23
23
  enabled: bool
24
+ p2p_enabled: bool # Whether to enable P2P for this rule (default: True)
24
25
 
25
26
 
26
27
  class AdminEntity(TypedDict):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: proxynt
3
- Version: 2.0.3
3
+ Version: 2.0.8
4
4
  Summary: UNKNOWN
5
5
  Home-page: https://github.com/sazima/proxynt
6
6
  License: UNKNOWN
@@ -0,0 +1,84 @@
1
+ LICENSE
2
+ MANIFEST.in
3
+ setup.py
4
+ ./__init__.py
5
+ ./run_client.py
6
+ ./run_server.py
7
+ ./client/__init__.py
8
+ ./client/clear_nonce_task.py
9
+ ./client/heart_beat_task.py
10
+ ./client/p2p_hole_punch.py
11
+ ./client/tcp_forward_client.py
12
+ ./client/udp_forward_client.py
13
+ ./common/__init__.py
14
+ ./common/encrypt_utils.py
15
+ ./common/logger_factory.py
16
+ ./common/nat_serialization.py
17
+ ./common/pool.py
18
+ ./common/register_append_data.py
19
+ ./common/speed_limit.py
20
+ ./common/crypto/__init__.py
21
+ ./common/crypto/table.py
22
+ ./common/websocket/__init__.py
23
+ ./common/websocket/_abnf.py
24
+ ./common/websocket/_app.py
25
+ ./common/websocket/_cookiejar.py
26
+ ./common/websocket/_core.py
27
+ ./common/websocket/_exceptions.py
28
+ ./common/websocket/_handshake.py
29
+ ./common/websocket/_http.py
30
+ ./common/websocket/_logging.py
31
+ ./common/websocket/_socket.py
32
+ ./common/websocket/_ssl_compat.py
33
+ ./common/websocket/_url.py
34
+ ./common/websocket/_utils.py
35
+ ./common/websocket/_wsdump.py
36
+ ./common/websocket/tests/__init__.py
37
+ ./common/websocket/tests/echo-server.py
38
+ ./common/websocket/tests/test_abnf.py
39
+ ./common/websocket/tests/test_app.py
40
+ ./common/websocket/tests/test_cookiejar.py
41
+ ./common/websocket/tests/test_http.py
42
+ ./common/websocket/tests/test_url.py
43
+ ./common/websocket/tests/test_websocket.py
44
+ ./constant/__init__.py
45
+ ./constant/message_type_constnat.py
46
+ ./constant/system_constant.py
47
+ ./context/__init__.py
48
+ ./context/context_utils.py
49
+ ./entity/__init__.py
50
+ ./entity/client_config_entity.py
51
+ ./entity/server_config_entity.py
52
+ ./entity/message/__init__.py
53
+ ./entity/message/message_entity.py
54
+ ./entity/message/push_config_entity.py
55
+ ./entity/message/tcp_over_websocket_message.py
56
+ ./exceptions/__init__.py
57
+ ./exceptions/duplicated_name.py
58
+ ./exceptions/invalid_password.py
59
+ ./exceptions/replay_error.py
60
+ ./exceptions/signature_error.py
61
+ ./server/__init__.py
62
+ ./server/admin_http_handler.py
63
+ ./server/tcp_forward_client.py
64
+ ./server/udp_forward_client.py
65
+ ./server/websocket_handler.py
66
+ ./server/task/__init__.py
67
+ ./server/task/check_cookie_task.py
68
+ ./server/task/clear_nonce_task.py
69
+ ./server/task/heart_beat_task.py
70
+ ./server/template/__init__.py
71
+ ./server/template/base.html
72
+ ./server/template/ele_index.html
73
+ ./server/template/login.html
74
+ ./server/template/css/index.css
75
+ ./server/template/css/fonts/element-icons.woff
76
+ ./server/template/js/axios.min.js
77
+ ./server/template/js/index.js
78
+ ./server/template/js/vue.min.js
79
+ proxynt.egg-info/PKG-INFO
80
+ proxynt.egg-info/SOURCES.txt
81
+ proxynt.egg-info/dependency_links.txt
82
+ proxynt.egg-info/entry_points.txt
83
+ proxynt.egg-info/requires.txt
84
+ proxynt.egg-info/top_level.txt