-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathproxy_server.py
More file actions
111 lines (85 loc) · 3.07 KB
/
proxy_server.py
File metadata and controls
111 lines (85 loc) · 3.07 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
"""Lightweight HTTP and SOCKS5 proxy server.
Environment variables:
LISTEN_HOST - Interface to bind (default: 0.0.0.0)
HTTP_PORT - HTTP proxy port (default: 8080)
SOCKS_PORT - SOCKS5 proxy port (default: 1080)
PROXY_USER - Optional username for auth
PROXY_PASS - Optional password for auth
"""
import asyncio
import logging
import os
import socket
import pproxy
DEFAULT_LISTEN_HOST = "0.0.0.0"
DEFAULT_HTTP_PORT = 8080
DEFAULT_SOCKS_PORT = 1080
def _build_auth_fragment() -> str:
user = os.getenv("PROXY_USER")
password = os.getenv("PROXY_PASS")
if user and password:
return f"#{user}:{password}"
if user or password:
raise ValueError("Both PROXY_USER and PROXY_PASS must be set together.")
return ""
def _env_int(name: str, default: int) -> int:
raw = os.getenv(name, str(default))
try:
return int(raw)
except ValueError as exc:
raise ValueError(f"{name} must be an integer (got {raw!r}).") from exc
def _local_ipv4_addresses() -> list[str]:
addresses: set[str] = set()
try:
hostname = socket.gethostname()
for info in socket.getaddrinfo(hostname, None, family=socket.AF_INET):
addr = info[4][0]
if addr and not addr.startswith("127."):
addresses.add(addr)
except OSError:
pass
try:
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:
sock.connect(("8.8.8.8", 80))
addr = sock.getsockname()[0]
if addr and not addr.startswith("127."):
addresses.add(addr)
except OSError:
pass
return sorted(addresses)
def _configure_logging() -> None:
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s | %(levelname)s | %(message)s",
)
async def main() -> None:
_configure_logging()
host = os.getenv("LISTEN_HOST", DEFAULT_LISTEN_HOST)
http_port = _env_int("HTTP_PORT", DEFAULT_HTTP_PORT)
socks_port = _env_int("SOCKS_PORT", DEFAULT_SOCKS_PORT)
auth = _build_auth_fragment()
http_uri = f"http://{host}:{http_port}{auth}"
socks_uri = f"socks5://{host}:{socks_port}{auth}"
args = {
"rserver": [], # direct outbound (uses system routing / VPN)
"verbose": lambda message: logging.info("pproxy: %s", message),
}
http_server = pproxy.Server(http_uri)
socks_server = pproxy.Server(socks_uri)
http_handler = await http_server.start_server(args)
socks_handler = await socks_server.start_server(args)
logging.info("HTTP proxy listening on %s:%s", host, http_port)
logging.info("SOCKS5 proxy listening on %s:%s", host, socks_port)
for addr in _local_ipv4_addresses():
logging.info("Use this LAN IP on other devices: %s", addr)
try:
await asyncio.Event().wait()
except KeyboardInterrupt:
logging.info("Shutdown requested by user.")
finally:
http_handler.close()
socks_handler.close()
await http_handler.wait_closed()
await socks_handler.wait_closed()
if __name__ == "__main__":
asyncio.run(main())