82 lines
2.4 KiB
Python
82 lines
2.4 KiB
Python
#!/usr/bin/env python3
|
|
"""test-nc-public.py - 模拟 frpc 行为:发 PROXY v1 + TLS ClientHello SNI=nc.gvsds.com 到 127.0.0.1:10443"""
|
|
import socket
|
|
import ssl
|
|
import time
|
|
import sys
|
|
|
|
HOST = "127.0.0.1"
|
|
PORT = 10443
|
|
SNI = "nc.gvsds.com"
|
|
PROXY_SRC_IP = "8.8.8.8" # 模拟客户端公网 IP
|
|
PROXY_SRC_PORT = 54321
|
|
|
|
def build_proxy_v1_header(src_ip, src_port, dst_ip, dst_port):
|
|
return f"PROXY TCP4 {src_ip} {dst_ip} {src_port} {dst_port}\r\n".encode("ascii")
|
|
|
|
def main():
|
|
# 1) 裸 TCP 到 10443
|
|
raw = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
raw.settimeout(180)
|
|
raw.connect((HOST, PORT))
|
|
|
|
# 2) 发 PROXY v1 header
|
|
proxy_hdr = build_proxy_v1_header(PROXY_SRC_IP, PROXY_SRC_PORT, HOST, PORT)
|
|
print(f" -> PROXY header: {proxy_hdr!r}")
|
|
raw.sendall(proxy_hdr)
|
|
|
|
# 3) 包 TLS
|
|
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
|
|
ctx.check_hostname = False
|
|
ctx.verify_mode = ssl.CERT_NONE
|
|
ctx.set_alpn_protocols(["http/1.1"]) # 强制 http/1.1,避免 h2 frame 复杂度
|
|
s = ctx.wrap_socket(raw, server_hostname=SNI)
|
|
print(f" TLS SNI: {SNI}, ALPN: {s.selected_alpn_protocol()}")
|
|
|
|
# 4) 发 HTTP/1.1(绕开 h2 SETTINGS frame 复杂度)
|
|
req = (
|
|
f"GET /login HTTP/1.1\r\n"
|
|
f"Host: {SNI}\r\n"
|
|
f"User-Agent: test-nc-public/1.0\r\n"
|
|
f"Accept: */*\r\n"
|
|
f"Connection: close\r\n"
|
|
f"\r\n"
|
|
).encode("ascii")
|
|
t0 = time.time()
|
|
s.sendall(req)
|
|
print(f" -> sent {len(req)} bytes request")
|
|
|
|
# 5) 读响应
|
|
chunks = []
|
|
while True:
|
|
try:
|
|
data = s.recv(65536)
|
|
except socket.timeout:
|
|
print(" [timeout 30s]")
|
|
break
|
|
if not data:
|
|
break
|
|
chunks.append(data)
|
|
s.close()
|
|
elapsed = time.time() - t0
|
|
|
|
body = b"".join(chunks)
|
|
head, _, body = body.partition(b"\r\n\r\n")
|
|
status_line = head.split(b"\r\n", 1)[0].decode("latin-1", errors="replace")
|
|
print(f" Status: {status_line}")
|
|
print(f" Body size: {len(body)} bytes")
|
|
print(f" Elapsed: {elapsed:.2f}s")
|
|
print(f" Body preview (前 500 字符):")
|
|
text = body[:500].decode("utf-8", errors="replace")
|
|
for line in text.splitlines()[:15]:
|
|
print(f" {line}")
|
|
|
|
return 0 if b"200 OK" in head or b"302" in head else 1
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
sys.exit(main())
|
|
except Exception as e:
|
|
print(f" EXCEPTION: {type(e).__name__}: {e}")
|
|
sys.exit(2)
|