Spaces:
Paused
Paused
| #!/usr/bin/env python3 | |
| import os, sys, socket, ssl, urllib.request, urllib.parse, struct, base64, select, threading, time | |
| WORKER_HOST = os.environ.get("WORKER_HOST", "egress-proxy.arjav-3003jain.workers.dev") | |
| PROXY_PORT = int(os.environ.get("LOCAL_PROXY_PORT", "8087")) | |
| BUF_SIZE = 65536 | |
| def log(msg): | |
| print(f"[proxy] {time.strftime('%H:%M:%S')} {msg}", flush=True) | |
| def http_proxy_req(data): | |
| try: | |
| lines = data.split(b"\r\n") | |
| parts = lines[0].decode("utf-8", errors="replace").split(" ") | |
| if len(parts) < 3: | |
| return None | |
| method, target_url = parts[0], parts[1] | |
| worker_url = f"https://{WORKER_HOST}/http?url={urllib.parse.quote(target_url, safe='')}" | |
| req = urllib.request.Request(worker_url, method=method) | |
| for line in lines[1:]: | |
| if b":" in line: | |
| k, v = line.split(b":", 1) | |
| k = k.strip().decode("utf-8", errors="replace").lower() | |
| if k in ("host", "proxy-connection", "proxy-authorization", "transfer-encoding"): | |
| continue | |
| req.add_header(k, v.strip().decode("utf-8", errors="replace")) | |
| if method in ("POST", "PUT", "PATCH"): | |
| req.data = data.split(b"\r\n\r\n", 1)[1] if b"\r\n\r\n" in data else b"" | |
| resp = urllib.request.urlopen(req, timeout=30) | |
| hdrs = f"HTTP/1.1 {resp.status} OK\r\n".encode() | |
| for k, v in resp.headers.items(): | |
| if k.lower() not in ("transfer-encoding", "content-encoding"): | |
| hdrs += f"{k}: {v}\r\n".encode() | |
| return hdrs + b"\r\n" + resp.read() | |
| except urllib.error.HTTPError as e: | |
| return f"HTTP/1.1 {e.code} Error\r\n\r\n{e.read().decode(errors='replace')}".encode() | |
| except Exception as e: | |
| return f"HTTP/1.1 502 Bad Gateway\r\n\r\nProxy error: {e}".encode() | |
| def recv_exact(sock, n): | |
| data = b"" | |
| while len(data) < n: | |
| chunk = sock.recv(n - len(data)) | |
| if not chunk: | |
| return None | |
| data += chunk | |
| return data | |
| def ws_send_frame(sock, data): | |
| mask_key = os.urandom(4) | |
| masked = bytes(b ^ mask_key[i % 4] for i, b in enumerate(data)) | |
| length = len(data) | |
| hdr = bytearray([0x82]) | |
| if length < 126: | |
| hdr.append(0x80 | length) | |
| elif length < 65536: | |
| hdr += bytearray([0x80 | 126]) + bytearray(struct.pack(">H", length)) | |
| else: | |
| hdr += bytearray([0x80 | 127]) + bytearray(struct.pack(">Q", length)) | |
| sock.sendall(bytes(hdr) + mask_key + masked) | |
| def ws_recv_frame(sock): | |
| while True: | |
| b1 = sock.recv(1) | |
| if not b1: | |
| return None | |
| op = b1[0] & 0x0F | |
| if op == 0x08: | |
| return None | |
| if op in (0x01, 0x02): | |
| break | |
| if op == 0x09: | |
| ws_send_frame(sock, b"") | |
| continue | |
| if op == 0x0A: | |
| continue | |
| b2 = sock.recv(1)[0] | |
| length = b2 & 0x7F | |
| if length == 126: | |
| raw = recv_exact(sock, 2) | |
| if raw is None: return None | |
| length = struct.unpack(">H", raw)[0] | |
| elif length == 127: | |
| raw = recv_exact(sock, 8) | |
| if raw is None: return None | |
| length = struct.unpack(">Q", raw)[0] | |
| return recv_exact(sock, length) | |
| def ws_connect_raw(host, port): | |
| ctx = ssl.create_default_context() | |
| sock = socket.create_connection((WORKER_HOST, 443), timeout=15) | |
| ssock = ctx.wrap_socket(sock, server_hostname=WORKER_HOST) | |
| key = base64.b64encode(os.urandom(16)).decode() | |
| req = ( | |
| f"GET /ws-tunnel?host={host}&port={port} HTTP/1.1\r\n" | |
| f"Host: {WORKER_HOST}\r\n" | |
| f"Upgrade: websocket\r\n" | |
| f"Connection: Upgrade\r\n" | |
| f"Sec-WebSocket-Key: {key}\r\n" | |
| f"Sec-WebSocket-Version: 13\r\n" | |
| f"\r\n" | |
| ) | |
| ssock.sendall(req.encode()) | |
| resp = b"" | |
| while b"\r\n\r\n" not in resp: | |
| chunk = ssock.recv(BUF_SIZE) | |
| if not chunk: | |
| raise ConnectionError("Worker hung up") | |
| resp += chunk | |
| if b"101" not in resp: | |
| raise ConnectionError(f"Upgrade failed: {resp.split(b'\r\n')[0].decode()}") | |
| return key, ssock | |
| def handle_connect(client_sock, addr, host, port): | |
| try: | |
| key, ws_sock = ws_connect_raw(host, port) | |
| except Exception as e: | |
| log(f"CONNECT {host}:{port} tunnel error: {e}") | |
| try: | |
| client_sock.sendall(b"HTTP/1.1 502 Bad Gateway\r\n\r\n") | |
| except: | |
| pass | |
| return | |
| try: | |
| client_sock.sendall(b"HTTP/1.1 200 Connection Established\r\n\r\n") | |
| except: | |
| try: | |
| ws_sock.close() | |
| except: | |
| pass | |
| return | |
| stop = threading.Event() | |
| def client_to_ws(): | |
| try: | |
| while not stop.is_set(): | |
| ready, _, _ = select.select([client_sock], [], [], 1) | |
| if not ready: | |
| continue | |
| data = client_sock.recv(BUF_SIZE) | |
| if not data: | |
| break | |
| ws_send_frame(ws_sock, data) | |
| except: | |
| pass | |
| finally: | |
| stop.set() | |
| def ws_to_client(): | |
| try: | |
| while not stop.is_set(): | |
| data = ws_recv_frame(ws_sock) | |
| if data is None: | |
| break | |
| client_sock.sendall(data) | |
| except: | |
| pass | |
| finally: | |
| stop.set() | |
| t1 = threading.Thread(target=client_to_ws, daemon=True) | |
| t2 = threading.Thread(target=ws_to_client, daemon=True) | |
| t1.start() | |
| t2.start() | |
| t1.join() | |
| t2.join() | |
| try: | |
| ws_sock.close() | |
| except: | |
| pass | |
| def handle_client(client_sock, addr): | |
| try: | |
| data = client_sock.recv(BUF_SIZE) | |
| if not data: | |
| return | |
| parts = data.split(b"\r\n")[0].decode("utf-8", errors="replace").split(" ") | |
| if parts[0] == "CONNECT": | |
| target = parts[1] | |
| host, port = (target.split(":") + ["443"])[:2] | |
| handle_connect(client_sock, addr, host, int(port)) | |
| else: | |
| response = http_proxy_req(data) | |
| if response: | |
| client_sock.sendall(response) | |
| except: | |
| pass | |
| finally: | |
| try: | |
| client_sock.close() | |
| except: | |
| pass | |
| def main(): | |
| srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |
| srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) | |
| srv.bind(("127.0.0.1", PROXY_PORT)) | |
| srv.listen(128) | |
| log(f"Listening 127.0.0.1:{PROXY_PORT} → {WORKER_HOST}") | |
| while True: | |
| c, a = srv.accept() | |
| threading.Thread(target=handle_client, args=(c, a), daemon=True).start() | |
| if __name__ == "__main__": | |
| main() | |