v9: multi-destination tunnel support (parspack, mequ, alipi)
Server: - /tunnel/<dest> routes: parspack (185.208.174.152:22), mequ (188.213.68.133:2022), alipi (10.66.66.2:22) - /tunnel without dest defaults to parspack Client (tunnel.py): - --destination / -d flag to pick target - Lists available destinations in --help Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
34
chat.py
34
chat.py
@@ -25,8 +25,12 @@ import html
|
||||
import urllib.parse
|
||||
|
||||
PORT = 9999
|
||||
VERSION = "8"
|
||||
TUNNEL_TARGET = ("185.208.174.152", 22)
|
||||
VERSION = "9"
|
||||
TUNNEL_TARGETS = {
|
||||
"parspack": ("185.208.174.152", 22),
|
||||
"mequ": ("188.213.68.133", 2022),
|
||||
"alipi": ("10.66.66.2", 22),
|
||||
}
|
||||
MAX_FILE_SIZE = 1 * 1024 * 1024 # 1 MB per file
|
||||
MAX_TOTAL_STORAGE = 50 * 1024 * 1024 # 50 MB total
|
||||
|
||||
@@ -380,10 +384,10 @@ def ws_make_frame(opcode, data):
|
||||
return frame + data
|
||||
|
||||
|
||||
async def handle_ws_tunnel(ws_reader, ws_writer):
|
||||
"""Bridge WebSocket frames <-> raw TCP to TUNNEL_TARGET."""
|
||||
async def handle_ws_tunnel(ws_reader, ws_writer, target):
|
||||
"""Bridge WebSocket frames <-> raw TCP to target (host, port)."""
|
||||
try:
|
||||
ssh_reader, ssh_writer = await asyncio.open_connection(*TUNNEL_TARGET)
|
||||
ssh_reader, ssh_writer = await asyncio.open_connection(*target)
|
||||
except Exception as e:
|
||||
ws_writer.write(ws_make_frame(0x8, struct.pack("!H", 1011) + str(e).encode()[:123]))
|
||||
await ws_writer.drain()
|
||||
@@ -569,8 +573,22 @@ async def handle_http(reader, writer, first_line):
|
||||
writer.close()
|
||||
return
|
||||
|
||||
# GET /tunnel — WebSocket upgrade for SSH tunnel
|
||||
if method == "GET" and path == "/tunnel":
|
||||
# GET /tunnel/<dest> — WebSocket upgrade for SSH tunnel
|
||||
if method == "GET" and path.startswith("/tunnel"):
|
||||
# Parse destination: /tunnel/parspack, /tunnel/mequ, or /tunnel (default: parspack)
|
||||
parts = path.strip("/").split("/")
|
||||
dest_name = parts[1] if len(parts) > 1 else "parspack"
|
||||
target = TUNNEL_TARGETS.get(dest_name)
|
||||
if not target:
|
||||
names = ", ".join(TUNNEL_TARGETS.keys())
|
||||
err = f"Unknown destination '{dest_name}'. Available: {names}".encode()
|
||||
writer.write(b"HTTP/1.1 404 Not Found\r\n")
|
||||
writer.write(f"Content-Length: {len(err)}\r\n".encode())
|
||||
writer.write(b"\r\n")
|
||||
writer.write(err)
|
||||
await writer.drain()
|
||||
writer.close()
|
||||
return
|
||||
ws_key = headers.get("sec-websocket-key", "")
|
||||
if not ws_key:
|
||||
writer.write(b"HTTP/1.1 400 Bad Request\r\nContent-Length: 0\r\n\r\n")
|
||||
@@ -586,7 +604,7 @@ async def handle_http(reader, writer, first_line):
|
||||
f"Sec-WebSocket-Accept: {accept}\r\n"
|
||||
f"\r\n".encode())
|
||||
await writer.drain()
|
||||
await handle_ws_tunnel(reader, writer)
|
||||
await handle_ws_tunnel(reader, writer, target)
|
||||
return
|
||||
|
||||
# 404
|
||||
|
||||
Reference in New Issue
Block a user