block-proxy 0.1.14 → 0.1.16
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.
- package/.claude/settings.local.json +88 -34
- package/.claude/skills/icon_generate/skill.md +45 -0
- package/CLAUDE.md +3 -3
- package/README.md +28 -187
- package/client/app.py +29 -9
- package/client/build.sh +6 -0
- package/client/config.py +1 -0
- package/client/config_window.py +36 -26
- package/client/icons/app.icns +0 -0
- package/client/icons/christmas-sock_light.png +0 -0
- package/client/icons/christmas-sock_light_bar.png +0 -0
- package/client/icons/christmas-sock_light_bar_off.png +0 -0
- package/client/icons/socks_on_G.png +0 -0
- package/client/icons/socks_on_G_bar.png +0 -0
- package/client/icons/socks_on_M.png +0 -0
- package/client/icons/socks_on_M_bar.png +0 -0
- package/client/proxy_core.py +305 -21
- package/client/tests/test_proxy_core_udp.py +466 -0
- package/config.json +3 -1
- package/package.json +1 -1
- package/socks5/server.js +74 -167
- package/wiki.md +287 -0
- package/client/icons/backup/app_example.png +0 -0
- package/client/icons/backup/christmas-sock_dark.png +0 -0
- package/client/icons/backup/christmas-sock_light.png +0 -0
- package/client/icons/backup/socks_on_G.png +0 -0
- package/client/icons/backup/socks_on_M.png +0 -0
- package/client/icons/christmas-sock_dark.png +0 -0
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import struct
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
|
8
|
+
from proxy_core import (
|
|
9
|
+
write_udp_frame, read_udp_frame, ProxyCore, _UdpRelayProtocol,
|
|
10
|
+
connect_upstream_udp_associate,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TestUdpFrameEncoding:
|
|
15
|
+
@staticmethod
|
|
16
|
+
async def _make_pipe():
|
|
17
|
+
"""Create a TCP loopback connection pair for testing."""
|
|
18
|
+
server_ready = asyncio.Event()
|
|
19
|
+
conns = []
|
|
20
|
+
|
|
21
|
+
async def on_connect(reader, writer):
|
|
22
|
+
conns.append((reader, writer))
|
|
23
|
+
server_ready.set()
|
|
24
|
+
|
|
25
|
+
server = await asyncio.start_server(on_connect, "127.0.0.1", 0)
|
|
26
|
+
port = server.sockets[0].getsockname()[1]
|
|
27
|
+
c_reader, c_writer = await asyncio.open_connection("127.0.0.1", port)
|
|
28
|
+
await asyncio.wait_for(server_ready.wait(), timeout=2)
|
|
29
|
+
s_reader, s_writer = conns[0]
|
|
30
|
+
server.close()
|
|
31
|
+
return c_reader, c_writer, s_reader, s_writer
|
|
32
|
+
|
|
33
|
+
def test_frame_round_trip(self):
|
|
34
|
+
async def _run():
|
|
35
|
+
c_reader, c_writer, s_reader, s_writer = await self._make_pipe()
|
|
36
|
+
|
|
37
|
+
payload = b"\x00\x00\x00\x01\x7f\x00\x00\x01\x00\x50test data"
|
|
38
|
+
await write_udp_frame(s_writer, payload)
|
|
39
|
+
result = await asyncio.wait_for(read_udp_frame(c_reader), timeout=2)
|
|
40
|
+
assert result == payload
|
|
41
|
+
|
|
42
|
+
c_writer.close()
|
|
43
|
+
s_writer.close()
|
|
44
|
+
|
|
45
|
+
asyncio.run(_run())
|
|
46
|
+
|
|
47
|
+
def test_multiple_frames(self):
|
|
48
|
+
async def _run():
|
|
49
|
+
c_reader, c_writer, s_reader, s_writer = await self._make_pipe()
|
|
50
|
+
|
|
51
|
+
payloads = [b"frame1_data", b"frame2_data_longer", b"f3"]
|
|
52
|
+
for p in payloads:
|
|
53
|
+
await write_udp_frame(s_writer, p)
|
|
54
|
+
|
|
55
|
+
for p in payloads:
|
|
56
|
+
result = await asyncio.wait_for(read_udp_frame(c_reader), timeout=2)
|
|
57
|
+
assert result == p
|
|
58
|
+
|
|
59
|
+
c_writer.close()
|
|
60
|
+
s_writer.close()
|
|
61
|
+
|
|
62
|
+
asyncio.run(_run())
|
|
63
|
+
|
|
64
|
+
def test_empty_frame_raises(self):
|
|
65
|
+
async def _run():
|
|
66
|
+
c_reader, c_writer, s_reader, s_writer = await self._make_pipe()
|
|
67
|
+
|
|
68
|
+
s_writer.write(struct.pack("!H", 0))
|
|
69
|
+
await s_writer.drain()
|
|
70
|
+
|
|
71
|
+
with pytest.raises(Exception, match="invalid UDP frame length"):
|
|
72
|
+
await asyncio.wait_for(read_udp_frame(c_reader), timeout=2)
|
|
73
|
+
|
|
74
|
+
c_writer.close()
|
|
75
|
+
s_writer.close()
|
|
76
|
+
|
|
77
|
+
asyncio.run(_run())
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class TestUdpRelayProtocol:
|
|
81
|
+
def test_datagram_received_writes_frame(self):
|
|
82
|
+
async def _run():
|
|
83
|
+
server_ready = asyncio.Event()
|
|
84
|
+
conns = []
|
|
85
|
+
|
|
86
|
+
async def on_connect(reader, writer):
|
|
87
|
+
conns.append((reader, writer))
|
|
88
|
+
server_ready.set()
|
|
89
|
+
|
|
90
|
+
server = await asyncio.start_server(on_connect, "127.0.0.1", 0)
|
|
91
|
+
port = server.sockets[0].getsockname()[1]
|
|
92
|
+
c_reader, c_writer = await asyncio.open_connection("127.0.0.1", port)
|
|
93
|
+
await asyncio.wait_for(server_ready.wait(), timeout=2)
|
|
94
|
+
s_reader, s_writer = conns[0]
|
|
95
|
+
server.close()
|
|
96
|
+
|
|
97
|
+
loop = asyncio.get_event_loop()
|
|
98
|
+
proto = _UdpRelayProtocol(s_writer, loop)
|
|
99
|
+
proto.connection_made(None)
|
|
100
|
+
|
|
101
|
+
data = b"\x00\x00\x00\x01\x08\x08\x08\x08\x00\x35dns_query"
|
|
102
|
+
proto.datagram_received(data, ("127.0.0.1", 54321))
|
|
103
|
+
|
|
104
|
+
assert proto.client_addr == ("127.0.0.1", 54321)
|
|
105
|
+
|
|
106
|
+
# Need to flush since datagram_received doesn't await drain
|
|
107
|
+
await asyncio.sleep(0.05)
|
|
108
|
+
frame = await asyncio.wait_for(read_udp_frame(c_reader), timeout=2)
|
|
109
|
+
assert frame == data
|
|
110
|
+
|
|
111
|
+
c_writer.close()
|
|
112
|
+
s_writer.close()
|
|
113
|
+
|
|
114
|
+
asyncio.run(_run())
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class TestMockSocks5UdpServer:
|
|
118
|
+
"""End-to-end test with a mock SOCKS5 server that supports UDP over TCP framing."""
|
|
119
|
+
|
|
120
|
+
@staticmethod
|
|
121
|
+
async def _mock_socks5_server(reader, writer):
|
|
122
|
+
# Auth negotiation
|
|
123
|
+
header = await reader.readexactly(2)
|
|
124
|
+
nmethods = header[1]
|
|
125
|
+
await reader.readexactly(nmethods)
|
|
126
|
+
writer.write(b"\x05\x00") # No auth
|
|
127
|
+
await writer.drain()
|
|
128
|
+
|
|
129
|
+
# Request
|
|
130
|
+
req = await reader.readexactly(4)
|
|
131
|
+
cmd = req[1]
|
|
132
|
+
atyp = req[3]
|
|
133
|
+
if atyp == 0x01:
|
|
134
|
+
await reader.readexactly(4 + 2)
|
|
135
|
+
elif atyp == 0x03:
|
|
136
|
+
length = (await reader.readexactly(1))[0]
|
|
137
|
+
await reader.readexactly(length + 2)
|
|
138
|
+
elif atyp == 0x04:
|
|
139
|
+
await reader.readexactly(16 + 2)
|
|
140
|
+
|
|
141
|
+
if cmd != 0x03:
|
|
142
|
+
writer.write(b"\x05\x07\x00\x01" + b"\x00" * 4 + b"\x00\x00")
|
|
143
|
+
await writer.drain()
|
|
144
|
+
writer.close()
|
|
145
|
+
return
|
|
146
|
+
|
|
147
|
+
# UDP ASSOCIATE success
|
|
148
|
+
writer.write(b"\x05\x00\x00\x01\x00\x00\x00\x00\x00\x00")
|
|
149
|
+
await writer.drain()
|
|
150
|
+
|
|
151
|
+
# Echo server: read frames and echo back with modified payload
|
|
152
|
+
try:
|
|
153
|
+
while True:
|
|
154
|
+
length_data = await reader.readexactly(2)
|
|
155
|
+
length = struct.unpack("!H", length_data)[0]
|
|
156
|
+
payload = await reader.readexactly(length)
|
|
157
|
+
|
|
158
|
+
# Parse SOCKS5 UDP header to find data offset, then echo with "REPLY:" prefix
|
|
159
|
+
if len(payload) < 10:
|
|
160
|
+
continue
|
|
161
|
+
atyp = payload[3]
|
|
162
|
+
if atyp == 0x01:
|
|
163
|
+
header_len = 10
|
|
164
|
+
elif atyp == 0x03:
|
|
165
|
+
header_len = 5 + payload[4] + 2
|
|
166
|
+
elif atyp == 0x04:
|
|
167
|
+
header_len = 22
|
|
168
|
+
else:
|
|
169
|
+
continue
|
|
170
|
+
|
|
171
|
+
udp_header = payload[:header_len]
|
|
172
|
+
udp_data = payload[header_len:]
|
|
173
|
+
response_data = b"REPLY:" + udp_data
|
|
174
|
+
response_payload = udp_header + response_data
|
|
175
|
+
|
|
176
|
+
frame = struct.pack("!H", len(response_payload)) + response_payload
|
|
177
|
+
writer.write(frame)
|
|
178
|
+
await writer.drain()
|
|
179
|
+
except (asyncio.IncompleteReadError, ConnectionResetError, BrokenPipeError):
|
|
180
|
+
pass
|
|
181
|
+
finally:
|
|
182
|
+
writer.close()
|
|
183
|
+
|
|
184
|
+
def test_udp_associate_end_to_end(self):
|
|
185
|
+
"""Test with ProxyCore in its own thread connecting to mock server."""
|
|
186
|
+
import socket as sock_mod
|
|
187
|
+
import threading
|
|
188
|
+
|
|
189
|
+
# Start mock server in a background thread with its own loop
|
|
190
|
+
mock_loop = asyncio.new_event_loop()
|
|
191
|
+
server_ready = threading.Event()
|
|
192
|
+
server_port_holder = [0]
|
|
193
|
+
stop_event = asyncio.Event()
|
|
194
|
+
|
|
195
|
+
def run_mock_server():
|
|
196
|
+
asyncio.set_event_loop(mock_loop)
|
|
197
|
+
async def _start():
|
|
198
|
+
server = await asyncio.start_server(
|
|
199
|
+
self._mock_socks5_server, "127.0.0.1", 0
|
|
200
|
+
)
|
|
201
|
+
server_port_holder[0] = server.sockets[0].getsockname()[1]
|
|
202
|
+
server_ready.set()
|
|
203
|
+
await stop_event.wait()
|
|
204
|
+
server.close()
|
|
205
|
+
await server.wait_closed()
|
|
206
|
+
try:
|
|
207
|
+
mock_loop.run_until_complete(_start())
|
|
208
|
+
except Exception:
|
|
209
|
+
pass
|
|
210
|
+
finally:
|
|
211
|
+
mock_loop.close()
|
|
212
|
+
|
|
213
|
+
t = threading.Thread(target=run_mock_server, daemon=True)
|
|
214
|
+
t.start()
|
|
215
|
+
server_ready.wait(timeout=5)
|
|
216
|
+
server_port = server_port_holder[0]
|
|
217
|
+
|
|
218
|
+
config = {
|
|
219
|
+
"server": {
|
|
220
|
+
"protocol": "socks5",
|
|
221
|
+
"address": "127.0.0.1",
|
|
222
|
+
"port": server_port,
|
|
223
|
+
"username": "",
|
|
224
|
+
"password": "",
|
|
225
|
+
"tls": False,
|
|
226
|
+
"allowInsecure": True,
|
|
227
|
+
},
|
|
228
|
+
"local": {
|
|
229
|
+
"socks_port": 0,
|
|
230
|
+
"http_port": 0,
|
|
231
|
+
"udp": True,
|
|
232
|
+
},
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
s = sock_mod.socket()
|
|
236
|
+
s.bind(("127.0.0.1", 0))
|
|
237
|
+
config["local"]["socks_port"] = s.getsockname()[1]
|
|
238
|
+
s.close()
|
|
239
|
+
s = sock_mod.socket()
|
|
240
|
+
s.bind(("127.0.0.1", 0))
|
|
241
|
+
config["local"]["http_port"] = s.getsockname()[1]
|
|
242
|
+
s.close()
|
|
243
|
+
|
|
244
|
+
proxy = ProxyCore()
|
|
245
|
+
proxy.start(config)
|
|
246
|
+
actual_socks_port = proxy.socks_port
|
|
247
|
+
|
|
248
|
+
import time
|
|
249
|
+
time.sleep(0.2)
|
|
250
|
+
|
|
251
|
+
try:
|
|
252
|
+
# Connect to proxy's local SOCKS5 port
|
|
253
|
+
ctrl_sock = sock_mod.socket(sock_mod.AF_INET, sock_mod.SOCK_STREAM)
|
|
254
|
+
ctrl_sock.settimeout(5)
|
|
255
|
+
ctrl_sock.connect(("127.0.0.1", actual_socks_port))
|
|
256
|
+
|
|
257
|
+
# SOCKS5 handshake
|
|
258
|
+
ctrl_sock.sendall(b"\x05\x01\x00")
|
|
259
|
+
resp = ctrl_sock.recv(2)
|
|
260
|
+
assert resp == b"\x05\x00"
|
|
261
|
+
|
|
262
|
+
# UDP ASSOCIATE
|
|
263
|
+
ctrl_sock.sendall(b"\x05\x03\x00\x01\x00\x00\x00\x00\x00\x00")
|
|
264
|
+
reply = ctrl_sock.recv(10)
|
|
265
|
+
assert len(reply) == 10
|
|
266
|
+
assert reply[1] == 0x00
|
|
267
|
+
relay_port = struct.unpack("!H", reply[8:10])[0]
|
|
268
|
+
assert relay_port > 0
|
|
269
|
+
|
|
270
|
+
# Send UDP to relay
|
|
271
|
+
udp_sock = sock_mod.socket(sock_mod.AF_INET, sock_mod.SOCK_DGRAM)
|
|
272
|
+
udp_sock.settimeout(5)
|
|
273
|
+
|
|
274
|
+
udp_header = b"\x00\x00\x00\x01\x08\x08\x08\x08\x00\x35"
|
|
275
|
+
udp_data = b"hello_udp"
|
|
276
|
+
udp_sock.sendto(udp_header + udp_data, ("127.0.0.1", relay_port))
|
|
277
|
+
|
|
278
|
+
# Receive response
|
|
279
|
+
response, _ = udp_sock.recvfrom(65535)
|
|
280
|
+
assert response[10:] == b"REPLY:hello_udp"
|
|
281
|
+
|
|
282
|
+
udp_sock.close()
|
|
283
|
+
ctrl_sock.close()
|
|
284
|
+
finally:
|
|
285
|
+
proxy.stop()
|
|
286
|
+
mock_loop.call_soon_threadsafe(stop_event.set)
|
|
287
|
+
|
|
288
|
+
def test_udp_associate_with_auth(self):
|
|
289
|
+
async def _mock_auth_server(reader, writer):
|
|
290
|
+
header = await reader.readexactly(2)
|
|
291
|
+
nmethods = header[1]
|
|
292
|
+
await reader.readexactly(nmethods)
|
|
293
|
+
writer.write(b"\x05\x02") # Username/password auth
|
|
294
|
+
await writer.drain()
|
|
295
|
+
|
|
296
|
+
# Read auth: [ver(1), ulen(1), username(ulen), plen(1), password(plen)]
|
|
297
|
+
ver = await reader.readexactly(1)
|
|
298
|
+
ulen_b = await reader.readexactly(1)
|
|
299
|
+
ulen = ulen_b[0]
|
|
300
|
+
username = (await reader.readexactly(ulen)).decode()
|
|
301
|
+
plen_b = await reader.readexactly(1)
|
|
302
|
+
plen = plen_b[0]
|
|
303
|
+
password = (await reader.readexactly(plen)).decode()
|
|
304
|
+
|
|
305
|
+
if username == "testuser" and password == "testpass":
|
|
306
|
+
writer.write(b"\x01\x00")
|
|
307
|
+
else:
|
|
308
|
+
writer.write(b"\x01\xff")
|
|
309
|
+
writer.close()
|
|
310
|
+
return
|
|
311
|
+
await writer.drain()
|
|
312
|
+
|
|
313
|
+
# Request
|
|
314
|
+
req = await reader.readexactly(4)
|
|
315
|
+
if req[3] == 0x01:
|
|
316
|
+
await reader.readexactly(6)
|
|
317
|
+
|
|
318
|
+
writer.write(b"\x05\x00\x00\x01\x00\x00\x00\x00\x00\x00")
|
|
319
|
+
await writer.drain()
|
|
320
|
+
|
|
321
|
+
# Keep connection alive briefly
|
|
322
|
+
try:
|
|
323
|
+
await asyncio.wait_for(reader.read(1), timeout=2)
|
|
324
|
+
except (asyncio.TimeoutError, ConnectionResetError):
|
|
325
|
+
pass
|
|
326
|
+
writer.close()
|
|
327
|
+
|
|
328
|
+
async def _run():
|
|
329
|
+
server = await asyncio.start_server(
|
|
330
|
+
_mock_auth_server, "127.0.0.1", 0
|
|
331
|
+
)
|
|
332
|
+
server_port = server.sockets[0].getsockname()[1]
|
|
333
|
+
|
|
334
|
+
config = {
|
|
335
|
+
"address": "127.0.0.1",
|
|
336
|
+
"port": server_port,
|
|
337
|
+
"username": "testuser",
|
|
338
|
+
"password": "testpass",
|
|
339
|
+
"tls": False,
|
|
340
|
+
"allowInsecure": True,
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
reader, writer = await connect_upstream_udp_associate(config)
|
|
344
|
+
assert reader is not None
|
|
345
|
+
assert writer is not None
|
|
346
|
+
writer.close()
|
|
347
|
+
await writer.wait_closed()
|
|
348
|
+
server.close()
|
|
349
|
+
await server.wait_closed()
|
|
350
|
+
|
|
351
|
+
asyncio.run(_run())
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
class TestUdpDisabled:
|
|
355
|
+
def test_http_protocol_rejects_udp(self):
|
|
356
|
+
async def _run():
|
|
357
|
+
import socket as sock_mod
|
|
358
|
+
|
|
359
|
+
config = {
|
|
360
|
+
"server": {
|
|
361
|
+
"protocol": "http",
|
|
362
|
+
"address": "127.0.0.1",
|
|
363
|
+
"port": 9999,
|
|
364
|
+
"username": "",
|
|
365
|
+
"password": "",
|
|
366
|
+
"tls": False,
|
|
367
|
+
"allowInsecure": True,
|
|
368
|
+
},
|
|
369
|
+
"local": {
|
|
370
|
+
"socks_port": 0,
|
|
371
|
+
"http_port": 0,
|
|
372
|
+
"udp": True,
|
|
373
|
+
},
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
s = sock_mod.socket()
|
|
377
|
+
s.bind(("127.0.0.1", 0))
|
|
378
|
+
socks_port = s.getsockname()[1]
|
|
379
|
+
s.close()
|
|
380
|
+
s = sock_mod.socket()
|
|
381
|
+
s.bind(("127.0.0.1", 0))
|
|
382
|
+
http_port = s.getsockname()[1]
|
|
383
|
+
s.close()
|
|
384
|
+
|
|
385
|
+
config["local"]["socks_port"] = socks_port
|
|
386
|
+
config["local"]["http_port"] = http_port
|
|
387
|
+
|
|
388
|
+
proxy = ProxyCore()
|
|
389
|
+
proxy.start(config)
|
|
390
|
+
actual_socks_port = proxy.socks_port
|
|
391
|
+
|
|
392
|
+
await asyncio.sleep(0.1)
|
|
393
|
+
|
|
394
|
+
reader, writer = await asyncio.open_connection("127.0.0.1", actual_socks_port)
|
|
395
|
+
writer.write(b"\x05\x01\x00")
|
|
396
|
+
await writer.drain()
|
|
397
|
+
await reader.readexactly(2)
|
|
398
|
+
|
|
399
|
+
# UDP ASSOCIATE
|
|
400
|
+
writer.write(b"\x05\x03\x00\x01\x00\x00\x00\x00\x00\x00")
|
|
401
|
+
await writer.drain()
|
|
402
|
+
|
|
403
|
+
reply = await asyncio.wait_for(reader.readexactly(10), timeout=5)
|
|
404
|
+
assert reply[1] == 0x07 # Command not supported
|
|
405
|
+
|
|
406
|
+
writer.close()
|
|
407
|
+
await writer.wait_closed()
|
|
408
|
+
proxy.stop()
|
|
409
|
+
|
|
410
|
+
asyncio.run(_run())
|
|
411
|
+
|
|
412
|
+
def test_udp_false_config_rejects_udp(self):
|
|
413
|
+
async def _run():
|
|
414
|
+
import socket as sock_mod
|
|
415
|
+
|
|
416
|
+
config = {
|
|
417
|
+
"server": {
|
|
418
|
+
"protocol": "socks5",
|
|
419
|
+
"address": "127.0.0.1",
|
|
420
|
+
"port": 9999,
|
|
421
|
+
"username": "",
|
|
422
|
+
"password": "",
|
|
423
|
+
"tls": False,
|
|
424
|
+
"allowInsecure": True,
|
|
425
|
+
},
|
|
426
|
+
"local": {
|
|
427
|
+
"socks_port": 0,
|
|
428
|
+
"http_port": 0,
|
|
429
|
+
"udp": False,
|
|
430
|
+
},
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
s = sock_mod.socket()
|
|
434
|
+
s.bind(("127.0.0.1", 0))
|
|
435
|
+
socks_port = s.getsockname()[1]
|
|
436
|
+
s.close()
|
|
437
|
+
s = sock_mod.socket()
|
|
438
|
+
s.bind(("127.0.0.1", 0))
|
|
439
|
+
http_port = s.getsockname()[1]
|
|
440
|
+
s.close()
|
|
441
|
+
|
|
442
|
+
config["local"]["socks_port"] = socks_port
|
|
443
|
+
config["local"]["http_port"] = http_port
|
|
444
|
+
|
|
445
|
+
proxy = ProxyCore()
|
|
446
|
+
proxy.start(config)
|
|
447
|
+
actual_socks_port = proxy.socks_port
|
|
448
|
+
|
|
449
|
+
await asyncio.sleep(0.1)
|
|
450
|
+
|
|
451
|
+
reader, writer = await asyncio.open_connection("127.0.0.1", actual_socks_port)
|
|
452
|
+
writer.write(b"\x05\x01\x00")
|
|
453
|
+
await writer.drain()
|
|
454
|
+
await reader.readexactly(2)
|
|
455
|
+
|
|
456
|
+
writer.write(b"\x05\x03\x00\x01\x00\x00\x00\x00\x00\x00")
|
|
457
|
+
await writer.drain()
|
|
458
|
+
|
|
459
|
+
reply = await asyncio.wait_for(reader.readexactly(10), timeout=5)
|
|
460
|
+
assert reply[1] == 0x07 # Command not supported
|
|
461
|
+
|
|
462
|
+
writer.close()
|
|
463
|
+
await writer.wait_closed()
|
|
464
|
+
proxy.stop()
|
|
465
|
+
|
|
466
|
+
asyncio.run(_run())
|
package/config.json
CHANGED