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.
- {proxynt-2.0.3/proxynt.egg-info → proxynt-2.0.8}/PKG-INFO +1 -1
- proxynt-2.0.8/client/p2p_hole_punch.py +257 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/client/tcp_forward_client.py +6 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/common/nat_serialization.py +16 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/constant/message_type_constnat.py +7 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/constant/system_constant.py +6 -1
- {proxynt-2.0.3 → proxynt-2.0.8}/entity/message/push_config_entity.py +8 -4
- {proxynt-2.0.3 → proxynt-2.0.8}/entity/server_config_entity.py +1 -0
- {proxynt-2.0.3 → proxynt-2.0.8/proxynt.egg-info}/PKG-INFO +1 -1
- proxynt-2.0.8/proxynt.egg-info/SOURCES.txt +84 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/run_client.py +177 -3
- {proxynt-2.0.3 → proxynt-2.0.8}/server/admin_http_handler.py +3 -1
- {proxynt-2.0.3 → proxynt-2.0.8}/server/task/heart_beat_task.py +4 -2
- {proxynt-2.0.3/build/lib/proxynt → proxynt-2.0.8}/server/template/ele_index.html +14 -4
- {proxynt-2.0.3 → proxynt-2.0.8}/server/websocket_handler.py +159 -48
- proxynt-2.0.3/proxynt.egg-info/SOURCES.txt +0 -165
- proxynt-2.0.3/server/template/base.html +0 -38
- proxynt-2.0.3/server/template/css/fonts/element-icons.woff +0 -0
- proxynt-2.0.3/server/template/css/index.css +0 -1
- proxynt-2.0.3/server/template/ele_index.html +0 -605
- proxynt-2.0.3/server/template/js/axios.min.js +0 -3
- proxynt-2.0.3/server/template/js/index.js +0 -1
- proxynt-2.0.3/server/template/js/vue.min.js +0 -6
- proxynt-2.0.3/server/template/login.html +0 -54
- {proxynt-2.0.3 → proxynt-2.0.8}/LICENSE +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/MANIFEST.in +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/__init__.py +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/client/__init__.py +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/client/clear_nonce_task.py +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/client/heart_beat_task.py +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/client/udp_forward_client.py +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/common/__init__.py +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/common/crypto/__init__.py +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/common/crypto/table.py +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/common/encrypt_utils.py +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/common/logger_factory.py +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/common/pool.py +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/common/register_append_data.py +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/common/speed_limit.py +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/common/websocket/__init__.py +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/common/websocket/_abnf.py +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/common/websocket/_app.py +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/common/websocket/_cookiejar.py +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/common/websocket/_core.py +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/common/websocket/_exceptions.py +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/common/websocket/_handshake.py +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/common/websocket/_http.py +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/common/websocket/_logging.py +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/common/websocket/_socket.py +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/common/websocket/_ssl_compat.py +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/common/websocket/_url.py +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/common/websocket/_utils.py +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/common/websocket/_wsdump.py +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/common/websocket/tests/__init__.py +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/common/websocket/tests/echo-server.py +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/common/websocket/tests/test_abnf.py +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/common/websocket/tests/test_app.py +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/common/websocket/tests/test_cookiejar.py +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/common/websocket/tests/test_http.py +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/common/websocket/tests/test_url.py +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/common/websocket/tests/test_websocket.py +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/constant/__init__.py +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/context/__init__.py +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/context/context_utils.py +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/entity/__init__.py +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/entity/client_config_entity.py +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/entity/message/__init__.py +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/entity/message/message_entity.py +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/entity/message/tcp_over_websocket_message.py +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/exceptions/__init__.py +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/exceptions/duplicated_name.py +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/exceptions/invalid_password.py +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/exceptions/replay_error.py +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/exceptions/signature_error.py +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/proxynt.egg-info/dependency_links.txt +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/proxynt.egg-info/entry_points.txt +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/proxynt.egg-info/requires.txt +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/proxynt.egg-info/top_level.txt +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/run_server.py +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/server/__init__.py +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/server/task/__init__.py +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/server/task/check_cookie_task.py +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/server/task/clear_nonce_task.py +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/server/tcp_forward_client.py +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/server/template/__init__.py +0 -0
- {proxynt-2.0.3/build/lib/proxynt → proxynt-2.0.8}/server/template/base.html +0 -0
- {proxynt-2.0.3/build/lib/proxynt → proxynt-2.0.8}/server/template/css/fonts/element-icons.woff +0 -0
- {proxynt-2.0.3/build/lib/proxynt → proxynt-2.0.8}/server/template/css/index.css +0 -0
- {proxynt-2.0.3/build/lib/proxynt → proxynt-2.0.8}/server/template/js/axios.min.js +0 -0
- {proxynt-2.0.3/build/lib/proxynt → proxynt-2.0.8}/server/template/js/index.js +0 -0
- {proxynt-2.0.3/build/lib/proxynt → proxynt-2.0.8}/server/template/js/vue.min.js +0 -0
- {proxynt-2.0.3/build/lib/proxynt → proxynt-2.0.8}/server/template/login.html +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/server/udp_forward_client.py +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/setup.cfg +0 -0
- {proxynt-2.0.3 → proxynt-2.0.8}/setup.py +0 -0
|
@@ -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.
|
|
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)
|
|
@@ -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
|