cfspider 1.7.4__py3-none-any.whl

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.
@@ -0,0 +1,572 @@
1
+ """
2
+ CFspider 内置 VLESS 客户端
3
+ 通过 WebSocket 连接 edgetunnel,提供本地 HTTP 代理
4
+ """
5
+
6
+ import socket
7
+ import struct
8
+ import threading
9
+ import ssl
10
+ import time
11
+ import uuid
12
+ from urllib.parse import urlparse
13
+
14
+
15
+ class VlessClient:
16
+ """VLESS 协议客户端"""
17
+
18
+ def __init__(self, ws_url, vless_uuid=None):
19
+ """
20
+ 初始化 VLESS 客户端
21
+
22
+ Args:
23
+ ws_url: edgetunnel WebSocket 地址,如 "wss://v2.kami666.xyz"
24
+ vless_uuid: VLESS UUID,如不提供则自动生成
25
+ """
26
+ self.ws_url = ws_url
27
+ self.vless_uuid = vless_uuid or str(uuid.uuid4())
28
+
29
+ parsed = urlparse(ws_url)
30
+ self.host = parsed.hostname
31
+ self.port = parsed.port or (443 if parsed.scheme == 'wss' else 80)
32
+ self.path = parsed.path or '/'
33
+ self.use_ssl = parsed.scheme == 'wss'
34
+
35
+ def _create_vless_header(self, target_host, target_port):
36
+ """创建 VLESS 请求头"""
37
+ # VLESS 协议版本
38
+ header = bytes([0])
39
+
40
+ # UUID (16 bytes)
41
+ uuid_bytes = uuid.UUID(self.vless_uuid).bytes
42
+ header += uuid_bytes
43
+
44
+ # 附加信息长度
45
+ header += bytes([0])
46
+
47
+ # 命令 (1 = TCP)
48
+ header += bytes([1])
49
+
50
+ # 目标端口
51
+ header += struct.pack('>H', target_port)
52
+
53
+ # 地址类型和地址
54
+ try:
55
+ # 尝试解析为 IPv4
56
+ socket.inet_aton(target_host)
57
+ header += bytes([1]) # IPv4
58
+ header += socket.inet_aton(target_host)
59
+ except socket.error:
60
+ try:
61
+ # 尝试解析为 IPv6
62
+ socket.inet_pton(socket.AF_INET6, target_host)
63
+ header += bytes([3]) # IPv6
64
+ header += socket.inet_pton(socket.AF_INET6, target_host)
65
+ except socket.error:
66
+ # 域名
67
+ header += bytes([2]) # 域名
68
+ domain_bytes = target_host.encode('utf-8')
69
+ header += bytes([len(domain_bytes)])
70
+ header += domain_bytes
71
+
72
+ return header
73
+
74
+ def _websocket_handshake(self, sock):
75
+ """执行 WebSocket 握手"""
76
+ import base64
77
+ import hashlib
78
+ import os
79
+
80
+ # 生成随机 key
81
+ key = base64.b64encode(os.urandom(16)).decode('utf-8')
82
+
83
+ # 构建握手请求
84
+ request = (
85
+ f"GET {self.path} HTTP/1.1\r\n"
86
+ f"Host: {self.host}\r\n"
87
+ f"Upgrade: websocket\r\n"
88
+ f"Connection: Upgrade\r\n"
89
+ f"Sec-WebSocket-Key: {key}\r\n"
90
+ f"Sec-WebSocket-Version: 13\r\n"
91
+ f"\r\n"
92
+ )
93
+
94
+ sock.sendall(request.encode('utf-8'))
95
+
96
+ # 读取响应
97
+ response = b''
98
+ while b'\r\n\r\n' not in response:
99
+ chunk = sock.recv(1024)
100
+ if not chunk:
101
+ raise Exception("WebSocket 握手失败")
102
+ response += chunk
103
+
104
+ if b'101' not in response:
105
+ raise Exception(f"WebSocket 握手失败: {response.decode('utf-8', errors='ignore')}")
106
+
107
+ return True
108
+
109
+ def _send_ws_frame(self, sock, data):
110
+ """发送 WebSocket 帧"""
111
+ import os
112
+
113
+ # 构建帧头
114
+ frame = bytes([0x82]) # Binary frame, FIN=1
115
+
116
+ length = len(data)
117
+ if length <= 125:
118
+ frame += bytes([0x80 | length]) # Masked
119
+ elif length <= 65535:
120
+ frame += bytes([0x80 | 126])
121
+ frame += struct.pack('>H', length)
122
+ else:
123
+ frame += bytes([0x80 | 127])
124
+ frame += struct.pack('>Q', length)
125
+
126
+ # 掩码
127
+ mask = os.urandom(4)
128
+ frame += mask
129
+
130
+ # 掩码数据
131
+ masked_data = bytes([data[i] ^ mask[i % 4] for i in range(len(data))])
132
+ frame += masked_data
133
+
134
+ sock.sendall(frame)
135
+
136
+ def _recv_ws_frame(self, sock):
137
+ """接收 WebSocket 帧"""
138
+ # 读取帧头
139
+ header = sock.recv(2)
140
+ if len(header) < 2:
141
+ return None
142
+
143
+ opcode = header[0] & 0x0F
144
+ masked = (header[1] & 0x80) != 0
145
+ length = header[1] & 0x7F
146
+
147
+ if length == 126:
148
+ length_bytes = sock.recv(2)
149
+ length = struct.unpack('>H', length_bytes)[0]
150
+ elif length == 127:
151
+ length_bytes = sock.recv(8)
152
+ length = struct.unpack('>Q', length_bytes)[0]
153
+
154
+ if masked:
155
+ mask = sock.recv(4)
156
+
157
+ # 读取数据
158
+ data = b''
159
+ while len(data) < length:
160
+ chunk = sock.recv(length - len(data))
161
+ if not chunk:
162
+ break
163
+ data += chunk
164
+
165
+ if masked:
166
+ data = bytes([data[i] ^ mask[i % 4] for i in range(len(data))])
167
+
168
+ # 处理关闭帧
169
+ if opcode == 0x08:
170
+ return None
171
+
172
+ return data
173
+
174
+ def connect(self, target_host, target_port):
175
+ """
176
+ 通过 VLESS 连接到目标
177
+
178
+ Returns:
179
+ VlessConnection: 可用于读写的连接对象
180
+ """
181
+ # 创建连接
182
+ sock = socket.create_connection((self.host, self.port), timeout=30)
183
+
184
+ if self.use_ssl:
185
+ context = ssl.create_default_context()
186
+ sock = context.wrap_socket(sock, server_hostname=self.host)
187
+
188
+ # WebSocket 握手
189
+ self._websocket_handshake(sock)
190
+
191
+ # 创建 VLESS 头(稍后与第一个数据包一起发送)
192
+ vless_header = self._create_vless_header(target_host, target_port)
193
+
194
+ return VlessConnection(sock, self, vless_header)
195
+
196
+
197
+ class VlessConnection:
198
+ """VLESS 连接封装"""
199
+
200
+ def __init__(self, sock, client, vless_header=None):
201
+ self.sock = sock
202
+ self.client = client
203
+ self.buffer = b''
204
+ self.first_response = True
205
+ self.vless_header = vless_header # 第一次发送时需要带上
206
+ self.first_send = True
207
+
208
+ def send(self, data):
209
+ """发送数据"""
210
+ if self.first_send and self.vless_header:
211
+ # 第一次发送时,将 VLESS 头和数据一起发送
212
+ self.client._send_ws_frame(self.sock, self.vless_header + data)
213
+ self.first_send = False
214
+ else:
215
+ self.client._send_ws_frame(self.sock, data)
216
+
217
+ def recv(self, size):
218
+ """接收数据"""
219
+ # 如果缓冲区不够,尝试接收更多数据
220
+ if len(self.buffer) < size:
221
+ try:
222
+ frame = self.client._recv_ws_frame(self.sock)
223
+ if frame:
224
+ # 第一个响应需要跳过 VLESS 响应头
225
+ if self.first_response and len(frame) >= 2:
226
+ addon_len = frame[1] if len(frame) > 1 else 0
227
+ frame = frame[2 + addon_len:]
228
+ self.first_response = False
229
+ self.buffer += frame
230
+ except:
231
+ pass
232
+
233
+ result = self.buffer[:size]
234
+ self.buffer = self.buffer[size:]
235
+ return result
236
+
237
+ def recv_all(self):
238
+ """接收所有可用数据"""
239
+ try:
240
+ self.sock.setblocking(False)
241
+ while True:
242
+ try:
243
+ frame = self.client._recv_ws_frame(self.sock)
244
+ if frame is None:
245
+ break
246
+
247
+ if self.first_response and len(frame) >= 2:
248
+ addon_len = frame[1] if len(frame) > 1 else 0
249
+ frame = frame[2 + addon_len:]
250
+ self.first_response = False
251
+
252
+ self.buffer += frame
253
+ except (BlockingIOError, ssl.SSLWantReadError):
254
+ break
255
+ finally:
256
+ self.sock.setblocking(True)
257
+
258
+ result = self.buffer
259
+ self.buffer = b''
260
+ return result
261
+
262
+ def close(self):
263
+ """关闭连接"""
264
+ try:
265
+ self.sock.close()
266
+ except:
267
+ pass
268
+
269
+
270
+ class LocalVlessProxy:
271
+ """本地 VLESS HTTP 代理服务器"""
272
+
273
+ def __init__(self, ws_url, vless_uuid=None):
274
+ """
275
+ 初始化本地代理
276
+
277
+ Args:
278
+ ws_url: edgetunnel WebSocket 地址
279
+ vless_uuid: VLESS UUID
280
+ """
281
+ self.ws_url = ws_url
282
+ self.vless_uuid = vless_uuid
283
+ self.server = None
284
+ self.thread = None
285
+ self.port = None
286
+ self.running = False
287
+
288
+ def start(self):
289
+ """启动代理服务器"""
290
+ # 找可用端口
291
+ self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
292
+ self.server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
293
+ self.server.bind(('127.0.0.1', 0))
294
+ self.port = self.server.getsockname()[1]
295
+ self.server.listen(10)
296
+
297
+ self.running = True
298
+ self.thread = threading.Thread(target=self._serve, daemon=True)
299
+ self.thread.start()
300
+
301
+ # 等待服务器就绪
302
+ time.sleep(0.1)
303
+ return self.port
304
+
305
+ def _serve(self):
306
+ """服务循环"""
307
+ self.server.settimeout(1)
308
+ while self.running:
309
+ try:
310
+ client, addr = self.server.accept()
311
+ handler = threading.Thread(
312
+ target=self._handle_client,
313
+ args=(client,),
314
+ daemon=True
315
+ )
316
+ handler.start()
317
+ except socket.timeout:
318
+ continue
319
+ except:
320
+ break
321
+
322
+ def _handle_client(self, client):
323
+ """处理客户端连接"""
324
+ try:
325
+ client.settimeout(30)
326
+
327
+ # 读取请求
328
+ request = b''
329
+ while b'\r\n\r\n' not in request:
330
+ chunk = client.recv(4096)
331
+ if not chunk:
332
+ return
333
+ request += chunk
334
+
335
+ # 解析请求
336
+ lines = request.split(b'\r\n')
337
+ first_line = lines[0].decode('utf-8')
338
+ parts = first_line.split(' ')
339
+
340
+ if len(parts) < 2:
341
+ return
342
+
343
+ method = parts[0]
344
+
345
+ if method == 'CONNECT':
346
+ # HTTPS 代理
347
+ target = parts[1]
348
+ if ':' in target:
349
+ host, port = target.rsplit(':', 1)
350
+ port = int(port)
351
+ else:
352
+ host = target
353
+ port = 443
354
+
355
+ self._handle_connect(client, host, port)
356
+ else:
357
+ # HTTP 代理
358
+ url = parts[1]
359
+ self._handle_http(client, method, url, request)
360
+
361
+ except Exception as e:
362
+ pass
363
+ finally:
364
+ try:
365
+ client.close()
366
+ except:
367
+ pass
368
+
369
+ def _handle_connect(self, client, host, port):
370
+ """处理 HTTPS CONNECT 请求"""
371
+ try:
372
+ # 连接到 VLESS
373
+ vless = VlessClient(self.ws_url, self.vless_uuid)
374
+ conn = vless.connect(host, port)
375
+
376
+ # 发送连接成功
377
+ client.sendall(b'HTTP/1.1 200 Connection Established\r\n\r\n')
378
+
379
+ # 双向转发(使用线程)
380
+ self._relay_bidirectional(client, conn)
381
+
382
+ except Exception as e:
383
+ try:
384
+ client.sendall(b'HTTP/1.1 502 Bad Gateway\r\n\r\n')
385
+ except:
386
+ pass
387
+
388
+ def _handle_http(self, client, method, url, original_request):
389
+ """处理 HTTP 请求"""
390
+ try:
391
+ parsed = urlparse(url)
392
+ host = parsed.hostname
393
+ port = parsed.port or 80
394
+ path = parsed.path or '/'
395
+ if parsed.query:
396
+ path += '?' + parsed.query
397
+
398
+ # 连接到 VLESS
399
+ vless = VlessClient(self.ws_url, self.vless_uuid)
400
+ conn = vless.connect(host, port)
401
+
402
+ # 重建请求
403
+ lines = original_request.split(b'\r\n')
404
+ lines[0] = f'{method} {path} HTTP/1.1'.encode('utf-8')
405
+
406
+ # 更新 Host 头
407
+ new_lines = [lines[0]]
408
+ has_host = False
409
+ for line in lines[1:]:
410
+ if line.lower().startswith(b'host:'):
411
+ new_lines.append(f'Host: {host}'.encode('utf-8'))
412
+ has_host = True
413
+ elif line.lower().startswith(b'proxy-'):
414
+ continue
415
+ else:
416
+ new_lines.append(line)
417
+
418
+ if not has_host:
419
+ new_lines.insert(1, f'Host: {host}'.encode('utf-8'))
420
+
421
+ request = b'\r\n'.join(new_lines)
422
+ conn.send(request)
423
+
424
+ # 读取响应并转发
425
+ self._relay_response(client, conn)
426
+
427
+ except Exception as e:
428
+ client.sendall(b'HTTP/1.1 502 Bad Gateway\r\n\r\n')
429
+
430
+ def _relay_bidirectional(self, client, conn):
431
+ """双向数据转发(使用线程)"""
432
+ import threading
433
+
434
+ stop_event = threading.Event()
435
+
436
+ def client_to_vless():
437
+ try:
438
+ while not stop_event.is_set():
439
+ try:
440
+ client.settimeout(1)
441
+ data = client.recv(8192)
442
+ if data:
443
+ conn.send(data)
444
+ else:
445
+ break
446
+ except socket.timeout:
447
+ continue
448
+ except:
449
+ break
450
+ finally:
451
+ stop_event.set()
452
+
453
+ def vless_to_client():
454
+ try:
455
+ while not stop_event.is_set():
456
+ try:
457
+ conn.sock.settimeout(1)
458
+ frame = self._recv_ws_frame_safe(conn)
459
+ if frame:
460
+ client.sendall(frame)
461
+ elif frame is None:
462
+ break
463
+ except socket.timeout:
464
+ continue
465
+ except:
466
+ break
467
+ finally:
468
+ stop_event.set()
469
+
470
+ t1 = threading.Thread(target=client_to_vless, daemon=True)
471
+ t2 = threading.Thread(target=vless_to_client, daemon=True)
472
+ t1.start()
473
+ t2.start()
474
+
475
+ # 等待任一方向结束
476
+ while not stop_event.is_set():
477
+ time.sleep(0.1)
478
+
479
+ conn.close()
480
+
481
+ def _recv_ws_frame_safe(self, conn):
482
+ """安全地接收 WebSocket 帧"""
483
+ try:
484
+ sock = conn.sock
485
+ header = sock.recv(2)
486
+ if len(header) < 2:
487
+ return None
488
+
489
+ opcode = header[0] & 0x0F
490
+ masked = (header[1] & 0x80) != 0
491
+ length = header[1] & 0x7F
492
+
493
+ if length == 126:
494
+ length_bytes = sock.recv(2)
495
+ length = struct.unpack('>H', length_bytes)[0]
496
+ elif length == 127:
497
+ length_bytes = sock.recv(8)
498
+ length = struct.unpack('>Q', length_bytes)[0]
499
+
500
+ if masked:
501
+ mask = sock.recv(4)
502
+
503
+ data = b''
504
+ while len(data) < length:
505
+ chunk = sock.recv(min(length - len(data), 8192))
506
+ if not chunk:
507
+ break
508
+ data += chunk
509
+
510
+ if masked:
511
+ data = bytes([data[i] ^ mask[i % 4] for i in range(len(data))])
512
+
513
+ if opcode == 0x08:
514
+ return None
515
+
516
+ # 跳过 VLESS 响应头
517
+ if conn.first_response and len(data) >= 2:
518
+ addon_len = data[1]
519
+ data = data[2 + addon_len:]
520
+ conn.first_response = False
521
+
522
+ return data
523
+ except:
524
+ return b''
525
+
526
+ def _relay_response(self, client, conn):
527
+ """转发 HTTP 响应"""
528
+ try:
529
+ # 读取响应
530
+ response = b''
531
+ while True:
532
+ data = conn.recv(8192)
533
+ if not data:
534
+ break
535
+ response += data
536
+ client.sendall(data)
537
+
538
+ # 检查是否完成
539
+ if b'\r\n\r\n' in response:
540
+ # 简单处理:继续读取直到没有数据
541
+ conn.sock.settimeout(0.5)
542
+ try:
543
+ while True:
544
+ data = conn.recv(8192)
545
+ if not data:
546
+ break
547
+ client.sendall(data)
548
+ except socket.timeout:
549
+ pass
550
+ break
551
+ finally:
552
+ conn.close()
553
+
554
+ def stop(self):
555
+ """停止代理服务器"""
556
+ self.running = False
557
+ if self.server:
558
+ try:
559
+ self.server.close()
560
+ except:
561
+ pass
562
+ self.server = None
563
+ self.thread = None
564
+ self.port = None
565
+
566
+ @property
567
+ def proxy_url(self):
568
+ """获取代理 URL"""
569
+ if self.port:
570
+ return f"http://127.0.0.1:{self.port}"
571
+ return None
572
+