proxynt 2.0.58__tar.gz → 2.0.59rc2__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.58 → proxynt-2.0.59rc2}/PKG-INFO +1 -1
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/client/tcp_forward_client.py +7 -1
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/client/udp_forward_client.py +26 -8
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/nat_serialization_v2.py +5 -6
- proxynt-2.0.59rc2/common/pool.py +96 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/constant/system_constant.py +1 -1
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/proxynt.egg-info/PKG-INFO +1 -1
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/server/tcp_forward_client.py +11 -12
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/server/websocket_handler.py +10 -3
- proxynt-2.0.58/common/pool.py +0 -203
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/LICENSE +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/MANIFEST.in +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/__init__.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/client/__init__.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/client/abstract_tunnel.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/client/clear_nonce_task.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/client/heart_beat_task.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/client/kcp_tunnel_impl.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/client/n4_tunnel_manager.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/client/quic_tunnel_impl.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/client/tunnel_protocol.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/__init__.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/cert_utils.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/crypto/__init__.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/crypto/table.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/encrypt_utils.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/kcp.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/logger_factory.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/n4_protocol.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/n4_punch.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/nat_serialization.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/nat_serialization_v1.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/register_append_data.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/speed_limit.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket/__init__.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket/_abnf.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket/_app.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket/_cookiejar.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket/_core.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket/_dispatcher.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket/_exceptions.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket/_handshake.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket/_http.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket/_logging.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket/_socket.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket/_ssl_compat.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket/_url.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket/_utils.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket/_wsdump.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket/tests/__init__.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket/tests/echo-server.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket/tests/test_abnf.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket/tests/test_app.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket/tests/test_cookiejar.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket/tests/test_dispatcher.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket/tests/test_handshake_large_response.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket/tests/test_http.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket/tests/test_large_payloads.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket/tests/test_logging_helpers.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket/tests/test_reconnect_bad_fd.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket/tests/test_socket.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket/tests/test_socket_bugs.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket/tests/test_ssl_compat.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket/tests/test_ssl_edge_cases.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket/tests/test_url.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket/tests/test_utils.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket/tests/test_websocket.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket2/__init__.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket2/compliance/autobahn-test-report-Feb-03-2021.html +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket2/setup.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket2/websocket/__init__.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket2/websocket/_abnf.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket2/websocket/_app.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket2/websocket/_cookiejar.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket2/websocket/_core.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket2/websocket/_dispatcher.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket2/websocket/_exceptions.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket2/websocket/_handshake.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket2/websocket/_http.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket2/websocket/_logging.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket2/websocket/_socket.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket2/websocket/_ssl_compat.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket2/websocket/_url.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket2/websocket/_utils.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket2/websocket/_wsdump.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket2/websocket/tests/__init__.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket2/websocket/tests/echo-server.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket2/websocket/tests/test_abnf.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket2/websocket/tests/test_app.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket2/websocket/tests/test_cookiejar.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket2/websocket/tests/test_dispatcher.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket2/websocket/tests/test_handshake_large_response.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket2/websocket/tests/test_http.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket2/websocket/tests/test_large_payloads.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket2/websocket/tests/test_logging_helpers.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket2/websocket/tests/test_reconnect_bad_fd.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket2/websocket/tests/test_socket.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket2/websocket/tests/test_socket_bugs.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket2/websocket/tests/test_ssl_compat.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket2/websocket/tests/test_ssl_edge_cases.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket2/websocket/tests/test_url.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket2/websocket/tests/test_utils.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket2/websocket/tests/test_websocket.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/constant/__init__.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/constant/message_type_constnat.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/context/__init__.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/context/context_utils.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/entity/__init__.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/entity/client_config_entity.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/entity/message/__init__.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/entity/message/message_entity.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/entity/message/push_config_entity.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/entity/message/tcp_over_websocket_message.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/entity/server_config_entity.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/exceptions/__init__.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/exceptions/duplicated_name.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/exceptions/invalid_password.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/exceptions/replay_error.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/exceptions/signature_error.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/p2ptest/__init__.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/p2ptest/client.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/p2ptest/n4.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/proxynt.egg-info/SOURCES.txt +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/proxynt.egg-info/dependency_links.txt +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/proxynt.egg-info/entry_points.txt +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/proxynt.egg-info/requires.txt +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/proxynt.egg-info/top_level.txt +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/run_client.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/run_server.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/server/__init__.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/server/admin_http_handler.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/server/n4.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/server/n4_signal_service.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/server/task/__init__.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/server/task/check_cookie_task.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/server/task/clear_nonce_task.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/server/task/heart_beat_task.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/server/template/__init__.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/server/template/base.html +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/server/template/css/fonts/element-icons.woff +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/server/template/css/index.css +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/server/template/ele_index.html +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/server/template/js/axios.min.js +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/server/template/js/index.js +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/server/template/js/vue.min.js +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/server/template/login.html +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/server/udp_forward_client.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/setup.cfg +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/setup.py +0 -0
- {proxynt-2.0.58 → proxynt-2.0.59rc2}/test_exchange.py +0 -0
|
@@ -176,7 +176,7 @@ class TcpForwardClient:
|
|
|
176
176
|
if data.speed_limiter and recv:
|
|
177
177
|
wait_time = data.speed_limiter.acquire(len(recv))
|
|
178
178
|
if wait_time > 0:
|
|
179
|
-
|
|
179
|
+
self.socket_event_loop.pause_and_resume_later(each, wait_time)
|
|
180
180
|
|
|
181
181
|
if self.tunnel_manager:
|
|
182
182
|
if self.tunnel_manager.send_data(connection.uid, recv):
|
|
@@ -366,9 +366,15 @@ class TcpForwardClient:
|
|
|
366
366
|
return
|
|
367
367
|
try:
|
|
368
368
|
s = connection.socket
|
|
369
|
+
s.settimeout(30)
|
|
369
370
|
s.sendall(msg)
|
|
371
|
+
s.settimeout(None)
|
|
370
372
|
if not msg:
|
|
371
373
|
self.close_connection(s)
|
|
374
|
+
except socket.timeout:
|
|
375
|
+
LoggerFactory.get_logger().warning('sendall timeout, closing uid: %s' % connection.uid.hex())
|
|
376
|
+
self.close_connection(s)
|
|
377
|
+
self.close_remote_socket(connection)
|
|
372
378
|
except Exception:
|
|
373
379
|
LoggerFactory.get_logger().error(traceback.format_exc())
|
|
374
380
|
self.close_remote_socket(connection)
|
|
@@ -188,24 +188,42 @@ class UdpForwardClient:
|
|
|
188
188
|
LoggerFactory.get_logger().error(traceback.format_exc())
|
|
189
189
|
|
|
190
190
|
def _udp_receive_loop(self):
|
|
191
|
+
import select as _select
|
|
191
192
|
LoggerFactory.get_logger().info('UDP receive thread started')
|
|
192
193
|
while self.running:
|
|
193
194
|
connections = list(self.uid_to_connection.values())
|
|
195
|
+
if not connections:
|
|
196
|
+
time.sleep(0.1)
|
|
197
|
+
continue
|
|
198
|
+
sock_to_conn = {}
|
|
194
199
|
for conn in connections:
|
|
195
200
|
try:
|
|
196
201
|
conn.socket.setblocking(False)
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
202
|
+
sock_to_conn[conn.socket] = conn
|
|
203
|
+
except Exception:
|
|
204
|
+
pass
|
|
205
|
+
if not sock_to_conn:
|
|
206
|
+
time.sleep(0.1)
|
|
207
|
+
continue
|
|
208
|
+
try:
|
|
209
|
+
readable, _, _ = _select.select(list(sock_to_conn.keys()), [], [], 0.5)
|
|
210
|
+
except (OSError, ValueError):
|
|
211
|
+
time.sleep(0.1)
|
|
212
|
+
continue
|
|
213
|
+
for sock in readable:
|
|
214
|
+
conn = sock_to_conn.get(sock)
|
|
215
|
+
if not conn:
|
|
216
|
+
continue
|
|
217
|
+
try:
|
|
218
|
+
data, addr = sock.recvfrom(65536)
|
|
219
|
+
if data:
|
|
220
|
+
self._handle_udp_data(conn, data, addr)
|
|
221
|
+
except (BlockingIOError, socket.error):
|
|
222
|
+
pass
|
|
203
223
|
except Exception as e:
|
|
204
224
|
LoggerFactory.get_logger().error('UDP data receive error: %s' % e)
|
|
205
225
|
LoggerFactory.get_logger().error(traceback.format_exc())
|
|
206
226
|
|
|
207
|
-
time.sleep(0.001)
|
|
208
|
-
|
|
209
227
|
def _handle_udp_data(self, conn: UdpSocketConnection, data: bytes, addr):
|
|
210
228
|
if conn.speed_limiter and data:
|
|
211
229
|
wait_time = conn.speed_limiter.acquire(len(data))
|
|
@@ -42,12 +42,11 @@ class NatSerializationV2:
|
|
|
42
42
|
|
|
43
43
|
# 处理 data_content,确保可以被 msgpack 序列化
|
|
44
44
|
if data_content is not None:
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
serializable_content = data_content
|
|
50
|
-
# 如果有 'data' 字段且需要压缩
|
|
45
|
+
serializable_content = data_content
|
|
46
|
+
# 如果有 'data' 字段且需要压缩,才复制一份避免修改原数据
|
|
47
|
+
if (compress and has_snappy and isinstance(data_content, dict)
|
|
48
|
+
and 'data' in data_content and data_content['data']):
|
|
49
|
+
serializable_content = dict(data_content)
|
|
51
50
|
if isinstance(serializable_content, dict) and 'data' in serializable_content:
|
|
52
51
|
if compress and has_snappy and serializable_content['data']:
|
|
53
52
|
original_size = len(serializable_content['data'])
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import socket
|
|
3
|
+
import threading
|
|
4
|
+
import time
|
|
5
|
+
import traceback
|
|
6
|
+
from selectors import DefaultSelector, EVENT_READ
|
|
7
|
+
|
|
8
|
+
from common.logger_factory import LoggerFactory
|
|
9
|
+
from common.register_append_data import ResisterAppendData
|
|
10
|
+
from constant.system_constant import SystemConstant
|
|
11
|
+
|
|
12
|
+
"""
|
|
13
|
+
单线程 selector 事件循环, 使用 modify 切换事件掩码
|
|
14
|
+
参考 shadowsocks eventloop 模式
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class SelectPool:
|
|
19
|
+
|
|
20
|
+
def __init__(self):
|
|
21
|
+
self.is_running = True
|
|
22
|
+
self.fileno_to_client = dict()
|
|
23
|
+
self.selector = DefaultSelector()
|
|
24
|
+
|
|
25
|
+
def stop(self):
|
|
26
|
+
self.is_running = False
|
|
27
|
+
|
|
28
|
+
def clear(self):
|
|
29
|
+
self.fileno_to_client.clear()
|
|
30
|
+
|
|
31
|
+
def register(self, s: socket.socket, data: ResisterAppendData):
|
|
32
|
+
self.fileno_to_client[s.fileno()] = s
|
|
33
|
+
self.selector.register(s, EVENT_READ, data)
|
|
34
|
+
|
|
35
|
+
def unregister(self, s: socket.socket):
|
|
36
|
+
fileno = -1
|
|
37
|
+
try:
|
|
38
|
+
fileno = s.fileno()
|
|
39
|
+
except Exception:
|
|
40
|
+
pass
|
|
41
|
+
if fileno in self.fileno_to_client:
|
|
42
|
+
self.fileno_to_client.pop(fileno)
|
|
43
|
+
try:
|
|
44
|
+
self.selector.unregister(s)
|
|
45
|
+
except (KeyError, ValueError):
|
|
46
|
+
pass
|
|
47
|
+
except OSError:
|
|
48
|
+
LoggerFactory.get_logger().error(traceback.format_exc())
|
|
49
|
+
|
|
50
|
+
def pause_reading(self, s: socket.socket):
|
|
51
|
+
"""暂停监听可读事件 (modify 掩码为 0)"""
|
|
52
|
+
try:
|
|
53
|
+
key = self.selector.get_key(s)
|
|
54
|
+
self.selector.modify(s, 0, key.data)
|
|
55
|
+
except (KeyError, ValueError):
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
def resume_reading(self, s: socket.socket):
|
|
59
|
+
"""恢复监听可读事件"""
|
|
60
|
+
try:
|
|
61
|
+
key = self.selector.get_key(s)
|
|
62
|
+
self.selector.modify(s, EVENT_READ, key.data)
|
|
63
|
+
except (KeyError, ValueError):
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
def pause_and_resume_later(self, s: socket.socket, delay_time: float):
|
|
67
|
+
"""暂停可读监听, delay_time 秒后恢复"""
|
|
68
|
+
self.pause_reading(s)
|
|
69
|
+
threading.Timer(delay_time, self.resume_reading, args=(s,)).start()
|
|
70
|
+
|
|
71
|
+
def run(self):
|
|
72
|
+
while True:
|
|
73
|
+
if not self.is_running:
|
|
74
|
+
time.sleep(1)
|
|
75
|
+
continue
|
|
76
|
+
try:
|
|
77
|
+
try:
|
|
78
|
+
ready = self.selector.select(timeout=SystemConstant.DEFAULT_TIMEOUT)
|
|
79
|
+
except OSError:
|
|
80
|
+
time.sleep(0.5)
|
|
81
|
+
continue
|
|
82
|
+
for key, mask in ready:
|
|
83
|
+
fileno = key.fd
|
|
84
|
+
client = self.fileno_to_client.get(fileno)
|
|
85
|
+
if client is None:
|
|
86
|
+
if LoggerFactory.get_logger().isEnabledFor(logging.DEBUG):
|
|
87
|
+
LoggerFactory.get_logger().debug('fd %s not in fileno_to_client' % fileno)
|
|
88
|
+
continue
|
|
89
|
+
data: ResisterAppendData = key.data
|
|
90
|
+
try:
|
|
91
|
+
data.callable_(client, data)
|
|
92
|
+
except Exception:
|
|
93
|
+
LoggerFactory.get_logger().error(traceback.format_exc())
|
|
94
|
+
except Exception:
|
|
95
|
+
LoggerFactory.get_logger().error(traceback.format_exc())
|
|
96
|
+
time.sleep(1)
|
|
@@ -111,13 +111,13 @@ class TcpForwardClient:
|
|
|
111
111
|
server_socket_list.append(server)
|
|
112
112
|
for c in client_connection_list:
|
|
113
113
|
c.socket.close()
|
|
114
|
-
|
|
114
|
+
self.socket_event_loop.unregister(c.socket)
|
|
115
115
|
self.socket_to_connection.pop(c.socket)
|
|
116
116
|
self.uid_to_connection.pop(c.uid)
|
|
117
117
|
for s in server_socket_list:
|
|
118
118
|
self.listen_socket_to_public_server.pop(s.socket_server)
|
|
119
119
|
try:
|
|
120
|
-
|
|
120
|
+
self.socket_event_loop.unregister(s.socket_server)
|
|
121
121
|
s.socket_server.shutdown(socket.SHUT_RDWR)
|
|
122
122
|
except OSError:
|
|
123
123
|
pass
|
|
@@ -164,11 +164,11 @@ class TcpForwardClient:
|
|
|
164
164
|
LoggerFactory.get_logger().error(f'Close error: {traceback.format_exc()}')
|
|
165
165
|
return
|
|
166
166
|
|
|
167
|
-
# ---
|
|
167
|
+
# --- 发送端限速:暂停读取,延迟恢复 ---
|
|
168
168
|
if data.speed_limiter and recv:
|
|
169
169
|
wait_time = data.speed_limiter.acquire(len(recv))
|
|
170
170
|
if wait_time > 0:
|
|
171
|
-
|
|
171
|
+
self.socket_event_loop.pause_and_resume_later(each, wait_time)
|
|
172
172
|
|
|
173
173
|
if LoggerFactory.get_logger().isEnabledFor(logging.DEBUG):
|
|
174
174
|
LoggerFactory.get_logger().debug(f'send to ws uid: {socket_connection.uid}, len: {len(recv)}')
|
|
@@ -235,9 +235,8 @@ class TcpForwardClient:
|
|
|
235
235
|
if uid not in self.uid_to_connection:
|
|
236
236
|
LoggerFactory.get_logger().debug(f'{message}, {uid} not in ')
|
|
237
237
|
return
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
async with self.uid_to_send_lock[uid]:
|
|
238
|
+
lock = self.uid_to_send_lock.setdefault(uid, AsyncioLock())
|
|
239
|
+
async with lock:
|
|
241
240
|
connection = self.uid_to_connection.get(uid)
|
|
242
241
|
if not connection:
|
|
243
242
|
return
|
|
@@ -273,7 +272,7 @@ class TcpForwardClient:
|
|
|
273
272
|
connection.socket_server.delete_client(connection)
|
|
274
273
|
# Ensure unregister before closing
|
|
275
274
|
try:
|
|
276
|
-
|
|
275
|
+
self.socket_event_loop.unregister(connection.socket)
|
|
277
276
|
except Exception as e:
|
|
278
277
|
LoggerFactory.get_logger().error(f'Error unregistering socket: {e}')
|
|
279
278
|
|
|
@@ -288,13 +287,13 @@ class TcpForwardClient:
|
|
|
288
287
|
def close_connection(self, connection: PublicSocketConnection):
|
|
289
288
|
try:
|
|
290
289
|
LoggerFactory.get_logger().info(f'Closing connection {connection.uid}')
|
|
291
|
-
# with self.close_lock:
|
|
292
290
|
uid = connection.uid
|
|
293
291
|
if uid not in self.uid_to_connection:
|
|
294
292
|
return
|
|
295
293
|
self.socket_event_loop.unregister(connection.socket)
|
|
296
|
-
self.uid_to_connection.pop(uid)
|
|
297
|
-
self.socket_to_connection.pop(connection.socket)
|
|
294
|
+
self.uid_to_connection.pop(uid, None)
|
|
295
|
+
self.socket_to_connection.pop(connection.socket, None)
|
|
296
|
+
self.uid_to_send_lock.pop(uid, None)
|
|
298
297
|
connection.socket_server.delete_client(connection)
|
|
299
298
|
connection.socket.close()
|
|
300
299
|
except Exception:
|
|
@@ -306,7 +305,7 @@ class TcpForwardClient:
|
|
|
306
305
|
s: socket.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
307
306
|
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
308
307
|
s.bind(('', port))
|
|
309
|
-
s.listen(
|
|
308
|
+
s.listen(256)
|
|
310
309
|
return s
|
|
311
310
|
|
|
312
311
|
def close(self):
|
|
@@ -4,7 +4,6 @@ import logging
|
|
|
4
4
|
import time
|
|
5
5
|
import traceback
|
|
6
6
|
from asyncio import Lock
|
|
7
|
-
from concurrent.futures import ThreadPoolExecutor
|
|
8
7
|
from json import JSONDecodeError
|
|
9
8
|
from typing import List, Dict, Set, Tuple
|
|
10
9
|
|
|
@@ -31,7 +30,7 @@ from exceptions.replay_error import ReplayError
|
|
|
31
30
|
from exceptions.signature_error import SignatureError
|
|
32
31
|
from server.tcp_forward_client import TcpForwardClient
|
|
33
32
|
|
|
34
|
-
|
|
33
|
+
MAX_CONCURRENT_MESSAGES = 64
|
|
35
34
|
|
|
36
35
|
|
|
37
36
|
class MyWebSocketaHandler(WebSocketHandler):
|
|
@@ -63,6 +62,7 @@ class MyWebSocketaHandler(WebSocketHandler):
|
|
|
63
62
|
|
|
64
63
|
def open(self, *args: str, **kwargs: str):
|
|
65
64
|
self.lock = Lock()
|
|
65
|
+
self._msg_sem = asyncio.Semaphore(MAX_CONCURRENT_MESSAGES)
|
|
66
66
|
self.client_name = None
|
|
67
67
|
self.version = None
|
|
68
68
|
|
|
@@ -115,7 +115,14 @@ class MyWebSocketaHandler(WebSocketHandler):
|
|
|
115
115
|
raise
|
|
116
116
|
|
|
117
117
|
def on_message(self, m_bytes):
|
|
118
|
-
asyncio.ensure_future(self.
|
|
118
|
+
asyncio.ensure_future(self._on_message_with_backpressure(m_bytes))
|
|
119
|
+
|
|
120
|
+
async def _on_message_with_backpressure(self, m_bytes):
|
|
121
|
+
await self._msg_sem.acquire()
|
|
122
|
+
try:
|
|
123
|
+
await self.on_message_async(m_bytes)
|
|
124
|
+
finally:
|
|
125
|
+
self._msg_sem.release()
|
|
119
126
|
|
|
120
127
|
async def on_message_async(self, message):
|
|
121
128
|
tcp_forward_client = TcpForwardClient.get_instance()
|
proxynt-2.0.58/common/pool.py
DELETED
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
import logging
|
|
3
|
-
import threading
|
|
4
|
-
import time
|
|
5
|
-
import weakref
|
|
6
|
-
from concurrent.futures import ThreadPoolExecutor
|
|
7
|
-
from selectors import DefaultSelector, EVENT_READ
|
|
8
|
-
|
|
9
|
-
import socket
|
|
10
|
-
import traceback
|
|
11
|
-
from typing import Dict, List, Set
|
|
12
|
-
|
|
13
|
-
from common.logger_factory import LoggerFactory
|
|
14
|
-
from common.register_append_data import ResisterAppendData
|
|
15
|
-
from constant.system_constant import SystemConstant
|
|
16
|
-
|
|
17
|
-
"""
|
|
18
|
-
这里只监听了 socket 的可读状态
|
|
19
|
-
"""
|
|
20
|
-
max_workers = 99
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
class SelectPool:
|
|
24
|
-
|
|
25
|
-
def __init__(self):
|
|
26
|
-
self.is_running = True
|
|
27
|
-
self.fileno_to_client: Dict[int, socket.socket] = dict()
|
|
28
|
-
self.selector = DefaultSelector()
|
|
29
|
-
self.waiting_register_socket: Set = set()
|
|
30
|
-
|
|
31
|
-
self.socket_to_register_lock: Dict[socket.socket, threading.Lock] = dict()
|
|
32
|
-
self.socket_to_recv_lock: Dict[socket.socket, threading.Lock] = dict()
|
|
33
|
-
self.executor = ThreadPoolExecutor(max_workers=max_workers) #
|
|
34
|
-
|
|
35
|
-
def stop(self):
|
|
36
|
-
self.is_running = False
|
|
37
|
-
|
|
38
|
-
def clear(self):
|
|
39
|
-
self.fileno_to_client.clear()
|
|
40
|
-
self.waiting_register_socket.clear()
|
|
41
|
-
self.socket_to_register_lock.clear()
|
|
42
|
-
self.socket_to_recv_lock.clear()
|
|
43
|
-
|
|
44
|
-
def register(self, s: socket.socket, data: ResisterAppendData):
|
|
45
|
-
self.socket_to_register_lock[s] = threading.Lock()
|
|
46
|
-
self.socket_to_recv_lock[s] = threading.Lock()
|
|
47
|
-
self.fileno_to_client[s.fileno()] = s
|
|
48
|
-
self.selector.register(s, EVENT_READ, data)
|
|
49
|
-
|
|
50
|
-
def unregister_and_register_delay(self, s: socket.socket, data: ResisterAppendData, delay_time: int):
|
|
51
|
-
"""取消注册, 并在指定秒后注册"""
|
|
52
|
-
|
|
53
|
-
def _register_again():
|
|
54
|
-
try:
|
|
55
|
-
if s not in self.socket_to_register_lock:
|
|
56
|
-
return
|
|
57
|
-
is_exceed, remain = data.speed_limiter.is_exceed()
|
|
58
|
-
if is_exceed:
|
|
59
|
-
# 再次延迟检测
|
|
60
|
-
if LoggerFactory.get_logger().isEnabledFor(logging.DEBUG):
|
|
61
|
-
LoggerFactory.get_logger().debug('delay register again, maybe next: %.2f seconds " ' % (remain / data.speed_limiter.max_speed))
|
|
62
|
-
threading.Timer(delay_time, _register_again).start()
|
|
63
|
-
return
|
|
64
|
-
with self.socket_to_register_lock[s]:
|
|
65
|
-
if s in self.waiting_register_socket: # 在等待列表中
|
|
66
|
-
self.waiting_register_socket.remove(s)
|
|
67
|
-
self.register(s, data)
|
|
68
|
-
except Exception:
|
|
69
|
-
LoggerFactory.get_logger().error(traceback.format_exc())
|
|
70
|
-
raise
|
|
71
|
-
|
|
72
|
-
if s in self.waiting_register_socket: # 不在等待列表中
|
|
73
|
-
return
|
|
74
|
-
if s not in self.socket_to_register_lock:
|
|
75
|
-
return
|
|
76
|
-
with self.socket_to_register_lock[s]:
|
|
77
|
-
try:
|
|
78
|
-
self.selector.unregister(s)
|
|
79
|
-
self.waiting_register_socket.add(s)
|
|
80
|
-
except OSError:
|
|
81
|
-
LoggerFactory.get_logger().error(traceback.format_exc())
|
|
82
|
-
threading.Timer(delay_time, _register_again).start()
|
|
83
|
-
|
|
84
|
-
async def async_unregister(self, s: socket.socket):
|
|
85
|
-
"""添加超时机制的异步取消注册"""
|
|
86
|
-
try:
|
|
87
|
-
await asyncio.wait_for(
|
|
88
|
-
asyncio.get_event_loop().run_in_executor(self.executor, self.unregister, s),
|
|
89
|
-
timeout=5 # 5秒超时
|
|
90
|
-
)
|
|
91
|
-
except asyncio.TimeoutError:
|
|
92
|
-
LoggerFactory.get_logger().error(f"Timeout unregistering socket {s}")
|
|
93
|
-
# 强制从跟踪字典中移除
|
|
94
|
-
if s.fileno() in self.fileno_to_client:
|
|
95
|
-
self.fileno_to_client.pop(s.fileno())
|
|
96
|
-
if s in self.socket_to_register_lock:
|
|
97
|
-
self.socket_to_register_lock.pop(s)
|
|
98
|
-
if s in self.socket_to_recv_lock:
|
|
99
|
-
self.socket_to_recv_lock.pop(s)
|
|
100
|
-
if s in self.waiting_register_socket:
|
|
101
|
-
self.waiting_register_socket.remove(s)
|
|
102
|
-
|
|
103
|
-
def unregister(self, s: socket.socket):
|
|
104
|
-
if s not in self.socket_to_register_lock:
|
|
105
|
-
LoggerFactory.get_logger().info('not register socket, skip')
|
|
106
|
-
return
|
|
107
|
-
with self.socket_to_register_lock[s]:
|
|
108
|
-
if s in self.waiting_register_socket:
|
|
109
|
-
self.waiting_register_socket.remove(s)
|
|
110
|
-
if s.fileno() in self.fileno_to_client:
|
|
111
|
-
self.fileno_to_client.pop(s.fileno())
|
|
112
|
-
try:
|
|
113
|
-
self.selector.unregister(s)
|
|
114
|
-
except KeyError:
|
|
115
|
-
# KeyError 代表已经注销
|
|
116
|
-
pass
|
|
117
|
-
except ValueError:
|
|
118
|
-
# ? value error 代表已经注销?
|
|
119
|
-
pass
|
|
120
|
-
except OSError:
|
|
121
|
-
LoggerFactory.get_logger().error(traceback.format_exc())
|
|
122
|
-
if s in self.socket_to_register_lock:
|
|
123
|
-
self.socket_to_register_lock.pop(s)
|
|
124
|
-
if s in self.socket_to_recv_lock:
|
|
125
|
-
self.socket_to_recv_lock.pop(s)
|
|
126
|
-
|
|
127
|
-
def run(self):
|
|
128
|
-
while True:
|
|
129
|
-
if not self.is_running:
|
|
130
|
-
time.sleep(1)
|
|
131
|
-
continue
|
|
132
|
-
try:
|
|
133
|
-
try:
|
|
134
|
-
ready = self.selector.select(timeout=SystemConstant.DEFAULT_TIMEOUT)
|
|
135
|
-
except OSError:
|
|
136
|
-
# 监听列表为空的时候, windows会有os error
|
|
137
|
-
# LoggerFactory.get_logger().warn
|
|
138
|
-
time.sleep(0.5)
|
|
139
|
-
continue
|
|
140
|
-
for key, mask in ready:
|
|
141
|
-
fileno = key.fd
|
|
142
|
-
client = self.fileno_to_client.get(fileno)
|
|
143
|
-
if client is None:
|
|
144
|
-
LoggerFactory.get_logger().warn(f'key error, {fileno}, self.fileno_to_client: {self.fileno_to_client}')
|
|
145
|
-
continue
|
|
146
|
-
data: ResisterAppendData = key.data
|
|
147
|
-
# lock = self.socket_to_recv_lock[client]
|
|
148
|
-
# if not lock.acquire(blocking=False):
|
|
149
|
-
# if LoggerFactory.get_logger().isEnabledFor(logging.DEBUG):
|
|
150
|
-
# LoggerFactory.get_logger().debug(f'lock continue')
|
|
151
|
-
# time.sleep(.005)
|
|
152
|
-
# continue # 已被其他线程处理,跳过
|
|
153
|
-
self.selector.unregister(client) # register 防止一直就绪状态 耗cpu
|
|
154
|
-
self.executor.submit(self._handle_client, client, data)
|
|
155
|
-
except Exception:
|
|
156
|
-
LoggerFactory.get_logger().error(traceback.format_exc())
|
|
157
|
-
time.sleep(1)
|
|
158
|
-
|
|
159
|
-
def _handle_client(self, client, data):
|
|
160
|
-
try:
|
|
161
|
-
data.callable_(client, data)
|
|
162
|
-
except Exception:
|
|
163
|
-
LoggerFactory.get_logger().error(traceback.format_exc())
|
|
164
|
-
finally:
|
|
165
|
-
lock = self.socket_to_register_lock.get(client)
|
|
166
|
-
if lock is not None:
|
|
167
|
-
if not lock.acquire(blocking=True): # 正在锁,跳过
|
|
168
|
-
return
|
|
169
|
-
try:
|
|
170
|
-
if client not in self.socket_to_register_lock:
|
|
171
|
-
LoggerFactory.get_logger().warning('[POOL] re-register skipped: lock removed, fd=%s' % client.fileno())
|
|
172
|
-
return
|
|
173
|
-
self.selector.register(client, EVENT_READ, data)
|
|
174
|
-
except Exception:
|
|
175
|
-
LoggerFactory.get_logger().error('[POOL] re-register failed fd=%s: %s' % (client.fileno(), traceback.format_exc()))
|
|
176
|
-
finally:
|
|
177
|
-
lock.release()
|
|
178
|
-
else:
|
|
179
|
-
if LoggerFactory.get_logger().isEnabledFor(logging.DEBUG):
|
|
180
|
-
LoggerFactory.get_logger().debug('[POOL] re-register skipped: lock is None, fd=%s' % client.fileno())
|
|
181
|
-
# self.
|
|
182
|
-
# if client in self.socket_to_recv_lock:
|
|
183
|
-
# self.socket_to_recv_lock[client].release()
|
|
184
|
-
|
|
185
|
-
# try:
|
|
186
|
-
# start = time.time()
|
|
187
|
-
# if client in self.socket_to_register_lock:
|
|
188
|
-
# with self.socket_to_register_lock[client]:
|
|
189
|
-
# try:
|
|
190
|
-
# self.selector.register(client, EVENT_READ, data)
|
|
191
|
-
# except Exception as e:
|
|
192
|
-
# LoggerFactory.get_logger().warning(f'register error {e}')
|
|
193
|
-
# else:
|
|
194
|
-
# LoggerFactory.get_logger().warning(f'client not in lock')
|
|
195
|
-
# if lock.locked():
|
|
196
|
-
# lock.release()
|
|
197
|
-
# else:
|
|
198
|
-
# LoggerFactory.get_logger().warning(f'lock not in lock')
|
|
199
|
-
# # if LoggerFactory.get_logger().isEnabledFor(logging.DEBUG):
|
|
200
|
-
# # LoggerFactory.get_logger().debug(f'register cost {time.time() - start} seconds')
|
|
201
|
-
# except Exception:
|
|
202
|
-
# LoggerFactory.get_logger().error(traceback.format_exc())
|
|
203
|
-
#
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket/tests/test_handshake_large_response.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket2/websocket/tests/test_large_payloads.py
RENAMED
|
File without changes
|
{proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket2/websocket/tests/test_logging_helpers.py
RENAMED
|
File without changes
|
{proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket2/websocket/tests/test_reconnect_bad_fd.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{proxynt-2.0.58 → proxynt-2.0.59rc2}/common/websocket2/websocket/tests/test_ssl_edge_cases.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|