mirror of
https://github.com/baoweise-bot/aimili-vpngate.git
synced 2026-09-08 01:06:48 +08:00
feat: add proxy authentication, limit concurrent connections, and allow configurable proxy port.
This commit is contained in:
+33
-17
@@ -350,8 +350,8 @@ def check_openvpn_process():
|
|||||||
if pid_dir.isdigit():
|
if pid_dir.isdigit():
|
||||||
try:
|
try:
|
||||||
with open(os.path.join('/proc', pid_dir, 'cmdline'), 'r') as f:
|
with open(os.path.join('/proc', pid_dir, 'cmdline'), 'r') as f:
|
||||||
cmd = f.read().split('\x00')[0]
|
cmd = f.read().replace('\x00', ' ')
|
||||||
if 'openvpn' in cmd:
|
if 'openvpn' in cmd and ('/opt/aimilivpn/vpngate_data' in cmd or '/opt/aimilivpn/vpngate_data/configs' in cmd):
|
||||||
return True
|
return True
|
||||||
except Exception:
|
except Exception:
|
||||||
continue
|
continue
|
||||||
@@ -466,15 +466,16 @@ def print_status():
|
|||||||
proxy_port = proxy_port
|
proxy_port = proxy_port
|
||||||
|
|
||||||
if proxy_host == "::":
|
if proxy_host == "::":
|
||||||
socks_addr = "127.0.0.1"
|
proxy_addr = "127.0.0.1"
|
||||||
elif ":" in proxy_host:
|
elif ":" in proxy_host:
|
||||||
socks_addr = f"[{proxy_host}]"
|
proxy_addr = f"[{proxy_host}]"
|
||||||
else:
|
else:
|
||||||
socks_addr = proxy_host
|
proxy_addr = proxy_host
|
||||||
|
|
||||||
print_line("【使用方法】")
|
print_line("【使用方法】")
|
||||||
print_line(f" export http_proxy=socks5://{socks_addr}:{proxy_port}")
|
print_line(f" export http_proxy=http://{proxy_addr}:{proxy_port}")
|
||||||
print_line(f" export https_proxy=socks5://{socks_addr}:{proxy_port}")
|
print_line(f" export https_proxy=http://{proxy_addr}:{proxy_port}")
|
||||||
|
print_line(f" # 也可用于 SOCKS5: socks5://{proxy_addr}:{proxy_port}")
|
||||||
print_line("=======================================================")
|
print_line("=======================================================")
|
||||||
|
|
||||||
def run_service_cmd(cmd):
|
def run_service_cmd(cmd):
|
||||||
@@ -678,6 +679,10 @@ def configure_port():
|
|||||||
if val:
|
if val:
|
||||||
port = int(val)
|
port = int(val)
|
||||||
if 1 <= port <= 65535:
|
if 1 <= port <= 65535:
|
||||||
|
if port == int(cfg.get('proxy_port', 7928)):
|
||||||
|
print("错误: 网页管理端口不能与代理出站端口相同。")
|
||||||
|
time.sleep(2)
|
||||||
|
continue
|
||||||
cfg['port'] = port
|
cfg['port'] = port
|
||||||
save_ui_cfg(cfg)
|
save_ui_cfg(cfg)
|
||||||
print(f"网页管理端口已更新为: {port}")
|
print(f"网页管理端口已更新为: {port}")
|
||||||
@@ -694,6 +699,10 @@ def configure_port():
|
|||||||
if val:
|
if val:
|
||||||
port = int(val)
|
port = int(val)
|
||||||
if 1024 <= port <= 65535:
|
if 1024 <= port <= 65535:
|
||||||
|
if port == int(cfg.get('port', 8787)):
|
||||||
|
print("错误: 代理出站端口不能与网页管理端口相同。")
|
||||||
|
time.sleep(2)
|
||||||
|
continue
|
||||||
cfg['proxy_port'] = port
|
cfg['proxy_port'] = port
|
||||||
save_ui_cfg(cfg)
|
save_ui_cfg(cfg)
|
||||||
print(f"代理出站端口已更新为: {port}")
|
print(f"代理出站端口已更新为: {port}")
|
||||||
@@ -1040,19 +1049,24 @@ while True:
|
|||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Write config JSON
|
# Write config JSON. Values are passed as argv to avoid breaking Python code
|
||||||
python3 -c "
|
# when username/password contain quotes, backslashes, or shell metacharacters.
|
||||||
|
python3 - "$AUTH_FILE" "$UI_PORT" "$SECRET_PATH" "$UI_USERNAME" "$UI_PASSWORD" <<'PY'
|
||||||
import json
|
import json
|
||||||
|
import sys
|
||||||
|
|
||||||
|
auth_file, ui_port, secret_path, username, password = sys.argv[1:6]
|
||||||
cfg = {
|
cfg = {
|
||||||
'host': '::',
|
"host": "::",
|
||||||
'port': int('$UI_PORT'),
|
"port": int(ui_port),
|
||||||
'secret_path': '$SECRET_PATH',
|
"proxy_port": 7928,
|
||||||
'username': '$UI_USERNAME',
|
"secret_path": secret_path,
|
||||||
'password': '$UI_PASSWORD'
|
"username": username,
|
||||||
|
"password": password,
|
||||||
}
|
}
|
||||||
with open('$AUTH_FILE', 'w', encoding='utf-8') as f:
|
with open(auth_file, "w", encoding="utf-8") as f:
|
||||||
json.dump(cfg, f, ensure_ascii=False, indent=2)
|
json.dump(cfg, f, ensure_ascii=False, indent=2)
|
||||||
"
|
PY
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 8. Start service
|
# 8. Start service
|
||||||
@@ -1132,12 +1146,14 @@ SECRET_PATH="EJsW2EeBo9lY"
|
|||||||
USERNAME="未配置"
|
USERNAME="未配置"
|
||||||
PASSWORD="未配置"
|
PASSWORD="未配置"
|
||||||
UI_PORT=8787
|
UI_PORT=8787
|
||||||
|
PROXY_PORT=7928
|
||||||
AUTH_FILE="${INSTALL_DIR}/vpngate_data/ui_auth.json"
|
AUTH_FILE="${INSTALL_DIR}/vpngate_data/ui_auth.json"
|
||||||
if [ -f "$AUTH_FILE" ]; then
|
if [ -f "$AUTH_FILE" ]; then
|
||||||
SECRET_PATH=$(python3 -c "import json; print(json.load(open('$AUTH_FILE')).get('secret_path', 'EJsW2EeBo9lY'))" 2>/dev/null || echo "EJsW2EeBo9lY")
|
SECRET_PATH=$(python3 -c "import json; print(json.load(open('$AUTH_FILE')).get('secret_path', 'EJsW2EeBo9lY'))" 2>/dev/null || echo "EJsW2EeBo9lY")
|
||||||
USERNAME=$(python3 -c "import json; print(json.load(open('$AUTH_FILE')).get('username', '未配置'))" 2>/dev/null || echo "未配置")
|
USERNAME=$(python3 -c "import json; print(json.load(open('$AUTH_FILE')).get('username', '未配置'))" 2>/dev/null || echo "未配置")
|
||||||
PASSWORD=$(python3 -c "import json; print(json.load(open('$AUTH_FILE')).get('password', '未配置'))" 2>/dev/null || echo "未配置")
|
PASSWORD=$(python3 -c "import json; print(json.load(open('$AUTH_FILE')).get('password', '未配置'))" 2>/dev/null || echo "未配置")
|
||||||
UI_PORT=$(python3 -c "import json; print(json.load(open('$AUTH_FILE')).get('port', 8787))" 2>/dev/null || echo "8787")
|
UI_PORT=$(python3 -c "import json; print(json.load(open('$AUTH_FILE')).get('port', 8787))" 2>/dev/null || echo "8787")
|
||||||
|
PROXY_PORT=$(python3 -c "import json; print(json.load(open('$AUTH_FILE')).get('proxy_port', 7928))" 2>/dev/null || echo "7928")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Get VPS public IP
|
# Get VPS public IP
|
||||||
@@ -1158,7 +1174,7 @@ if [ -n "$PUBLIC_IPV6" ]; then
|
|||||||
fi
|
fi
|
||||||
echo -e " * 网页管理账号: ${YELLOW}${USERNAME}${PLAIN}"
|
echo -e " * 网页管理账号: ${YELLOW}${USERNAME}${PLAIN}"
|
||||||
echo -e " * 网页管理密码: ${YELLOW}${PASSWORD}${PLAIN}"
|
echo -e " * 网页管理密码: ${YELLOW}${PASSWORD}${PLAIN}"
|
||||||
echo -e " * HTTP/SOCKS5 代理端口: ${BLUE}http://127.0.0.1:7928/${PLAIN} 或 ${BLUE}http://[::1]:7928/${PLAIN}"
|
echo -e " * HTTP/SOCKS5 代理端口: ${BLUE}http://127.0.0.1:${PROXY_PORT}/${PLAIN} 或 ${BLUE}http://[::1]:${PROXY_PORT}/${PLAIN}"
|
||||||
echo -e " --------------------------------------------------------"
|
echo -e " --------------------------------------------------------"
|
||||||
echo -e " * 快速状态指令: ${YELLOW}ml status${PLAIN} 或 ${YELLOW}ml${PLAIN}"
|
echo -e " * 快速状态指令: ${YELLOW}ml status${PLAIN} 或 ${YELLOW}ml${PLAIN}"
|
||||||
echo -e " * 查看实时日志: ${YELLOW}ml logs${PLAIN}"
|
echo -e " * 查看实时日志: ${YELLOW}ml logs${PLAIN}"
|
||||||
|
|||||||
+151
-41
@@ -1,5 +1,8 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
import base64
|
||||||
|
import os
|
||||||
|
import secrets
|
||||||
import select
|
import select
|
||||||
import socket
|
import socket
|
||||||
import threading
|
import threading
|
||||||
@@ -7,6 +10,15 @@ import urllib.parse
|
|||||||
import time
|
import time
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
def parse_positive_int(value: str | None, default: int) -> int:
|
||||||
|
try:
|
||||||
|
return max(1, int(value or default))
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return default
|
||||||
|
|
||||||
|
MAX_PROXY_CONNECTIONS = parse_positive_int(os.environ.get("LOCAL_PROXY_MAX_CONNECTIONS"), 256)
|
||||||
|
proxy_connection_sem = threading.BoundedSemaphore(MAX_PROXY_CONNECTIONS)
|
||||||
|
|
||||||
def parse_int(value: Any) -> int:
|
def parse_int(value: Any) -> int:
|
||||||
try:
|
try:
|
||||||
return int(value)
|
return int(value)
|
||||||
@@ -22,18 +34,57 @@ def recv_exact(sock: socket.socket, size: int) -> bytes:
|
|||||||
data += chunk
|
data += chunk
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def resolve_dns_over_tun0(host: str, dns_server: str = "8.8.8.8", timeout: float = 3.0) -> str | None:
|
def parse_host_port(authority: str, default_port: int) -> tuple[str, int]:
|
||||||
try:
|
authority = authority.strip()
|
||||||
socket.inet_aton(host)
|
if authority.startswith("["):
|
||||||
return host
|
host_part, sep, rest = authority.partition("]")
|
||||||
except OSError:
|
host = host_part.lstrip("[")
|
||||||
pass
|
port = default_port
|
||||||
try:
|
if sep and rest.startswith(":"):
|
||||||
socket.inet_pton(socket.AF_INET6, host)
|
port_text = rest[1:]
|
||||||
return host
|
port = parse_int(port_text) or default_port
|
||||||
except OSError:
|
return host, port
|
||||||
pass
|
if authority.count(":") == 1:
|
||||||
|
host, _, port_text = authority.rpartition(":")
|
||||||
|
return host, parse_int(port_text) or default_port
|
||||||
|
return authority, default_port
|
||||||
|
|
||||||
|
def get_proxy_credentials() -> tuple[str | None, str | None]:
|
||||||
|
user = os.environ.get("LOCAL_PROXY_USER") or os.environ.get("LOCAL_PROXY_USERNAME")
|
||||||
|
password = os.environ.get("LOCAL_PROXY_PASS") or os.environ.get("LOCAL_PROXY_PASSWORD")
|
||||||
|
if user is None and password is None:
|
||||||
|
return None, None
|
||||||
|
return user or "", password or ""
|
||||||
|
|
||||||
|
def proxy_auth_enabled() -> bool:
|
||||||
|
user, password = get_proxy_credentials()
|
||||||
|
return user is not None and password is not None
|
||||||
|
|
||||||
|
def parse_http_basic_auth(lines: list[str]) -> tuple[str | None, str | None]:
|
||||||
|
for line in lines:
|
||||||
|
name, sep, value = line.partition(":")
|
||||||
|
if not sep or name.strip().lower() != "proxy-authorization":
|
||||||
|
continue
|
||||||
|
scheme, _, token = value.strip().partition(" ")
|
||||||
|
if scheme.lower() != "basic" or not token:
|
||||||
|
return None, None
|
||||||
|
try:
|
||||||
|
decoded = base64.b64decode(token, validate=True).decode("utf-8", errors="replace")
|
||||||
|
except Exception:
|
||||||
|
return None, None
|
||||||
|
username, sep, password = decoded.partition(":")
|
||||||
|
if not sep:
|
||||||
|
return None, None
|
||||||
|
return username, password
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
def check_credentials(username: str | None, password: str | None) -> bool:
|
||||||
|
expected_user, expected_pass = get_proxy_credentials()
|
||||||
|
if expected_user is None or expected_pass is None:
|
||||||
|
return True
|
||||||
|
return secrets.compare_digest(username or "", expected_user) and secrets.compare_digest(password or "", expected_pass)
|
||||||
|
|
||||||
|
def dns_query_over_tun0(host: str, qtype: int, dns_server: str, timeout: float) -> str | None:
|
||||||
import random
|
import random
|
||||||
sock = None
|
sock = None
|
||||||
try:
|
try:
|
||||||
@@ -52,7 +103,7 @@ def resolve_dns_over_tun0(host: str, dns_server: str = "8.8.8.8", timeout: float
|
|||||||
qname += len(part_bytes).to_bytes(1, "big") + part_bytes
|
qname += len(part_bytes).to_bytes(1, "big") + part_bytes
|
||||||
qname += b"\x00"
|
qname += b"\x00"
|
||||||
|
|
||||||
qtype_qclass = b"\x00\x01\x00\x01"
|
qtype_qclass = qtype.to_bytes(2, "big") + b"\x00\x01"
|
||||||
packet = tx_id + flags + questions + rrs + qname + qtype_qclass
|
packet = tx_id + flags + questions + rrs + qname + qtype_qclass
|
||||||
|
|
||||||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
@@ -66,7 +117,7 @@ def resolve_dns_over_tun0(host: str, dns_server: str = "8.8.8.8", timeout: float
|
|||||||
print("[DNS 绑定失败] [错误代码 3004] DNS 解析绑定 tun0 失败,网卡设备不存在,请检查 VPN 连接!", flush=True)
|
print("[DNS 绑定失败] [错误代码 3004] DNS 解析绑定 tun0 失败,网卡设备不存在,请检查 VPN 连接!", flush=True)
|
||||||
return None
|
return None
|
||||||
sock.sendto(packet, (dns_server, 53))
|
sock.sendto(packet, (dns_server, 53))
|
||||||
resp, _ = sock.recvfrom(2048)
|
resp, _ = sock.recvfrom(4096)
|
||||||
except Exception:
|
except Exception:
|
||||||
return None
|
return None
|
||||||
finally:
|
finally:
|
||||||
@@ -77,11 +128,8 @@ def resolve_dns_over_tun0(host: str, dns_server: str = "8.8.8.8", timeout: float
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if len(resp) < 12:
|
if len(resp) < 12 or resp[:2] != tx_id:
|
||||||
return None
|
return None
|
||||||
if resp[:2] != tx_id:
|
|
||||||
return None
|
|
||||||
|
|
||||||
rcode = resp[3] & 0x0F
|
rcode = resp[3] & 0x0F
|
||||||
if rcode != 0:
|
if rcode != 0:
|
||||||
return None
|
return None
|
||||||
@@ -92,17 +140,13 @@ def resolve_dns_over_tun0(host: str, dns_server: str = "8.8.8.8", timeout: float
|
|||||||
if length == 0:
|
if length == 0:
|
||||||
offset += 1
|
offset += 1
|
||||||
break
|
break
|
||||||
elif (length & 0xC0) == 0xC0:
|
if (length & 0xC0) == 0xC0:
|
||||||
offset += 2
|
offset += 2
|
||||||
break
|
break
|
||||||
else:
|
offset += 1 + length
|
||||||
offset += 1 + length
|
|
||||||
|
|
||||||
offset += 4
|
offset += 4
|
||||||
answers_count = int.from_bytes(resp[6:8], "big")
|
answers_count = int.from_bytes(resp[6:8], "big")
|
||||||
if answers_count == 0:
|
|
||||||
return None
|
|
||||||
|
|
||||||
for _ in range(answers_count):
|
for _ in range(answers_count):
|
||||||
if offset >= len(resp):
|
if offset >= len(resp):
|
||||||
break
|
break
|
||||||
@@ -111,11 +155,10 @@ def resolve_dns_over_tun0(host: str, dns_server: str = "8.8.8.8", timeout: float
|
|||||||
if length == 0:
|
if length == 0:
|
||||||
offset += 1
|
offset += 1
|
||||||
break
|
break
|
||||||
elif (length & 0xC0) == 0xC0:
|
if (length & 0xC0) == 0xC0:
|
||||||
offset += 2
|
offset += 2
|
||||||
break
|
break
|
||||||
else:
|
offset += 1 + length
|
||||||
offset += 1 + length
|
|
||||||
if offset + 10 > len(resp):
|
if offset + 10 > len(resp):
|
||||||
break
|
break
|
||||||
atype = int.from_bytes(resp[offset : offset + 2], "big")
|
atype = int.from_bytes(resp[offset : offset + 2], "big")
|
||||||
@@ -124,14 +167,30 @@ def resolve_dns_over_tun0(host: str, dns_server: str = "8.8.8.8", timeout: float
|
|||||||
offset += 10
|
offset += 10
|
||||||
if offset + rdlength > len(resp):
|
if offset + rdlength > len(resp):
|
||||||
break
|
break
|
||||||
if atype == 1 and aclass == 1 and rdlength == 4:
|
record = resp[offset : offset + rdlength]
|
||||||
ip_bytes = resp[offset : offset + 4]
|
if atype == qtype and aclass == 1:
|
||||||
return socket.inet_ntoa(ip_bytes)
|
if qtype == 1 and rdlength == 4:
|
||||||
|
return socket.inet_ntoa(record)
|
||||||
|
if qtype == 28 and rdlength == 16:
|
||||||
|
return socket.inet_ntop(socket.AF_INET6, record)
|
||||||
offset += rdlength
|
offset += rdlength
|
||||||
except Exception:
|
except Exception:
|
||||||
return None
|
return None
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def resolve_dns_over_tun0(host: str, dns_server: str = "8.8.8.8", timeout: float = 3.0) -> str | None:
|
||||||
|
try:
|
||||||
|
socket.inet_aton(host)
|
||||||
|
return host
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
socket.inet_pton(socket.AF_INET6, host)
|
||||||
|
return host
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
return dns_query_over_tun0(host, 1, dns_server, timeout) or dns_query_over_tun0(host, 28, dns_server, timeout)
|
||||||
|
|
||||||
def create_connection(address: tuple[str, int], timeout: float = 20) -> socket.socket:
|
def create_connection(address: tuple[str, int], timeout: float = 20) -> socket.socket:
|
||||||
host, port = address
|
host, port = address
|
||||||
resolved_ip = resolve_dns_over_tun0(host)
|
resolved_ip = resolve_dns_over_tun0(host)
|
||||||
@@ -178,8 +237,24 @@ def socks5_client(client: socket.socket, first_byte: bytes) -> None:
|
|||||||
upstream = None
|
upstream = None
|
||||||
try:
|
try:
|
||||||
methods_count = recv_exact(client, 1)[0]
|
methods_count = recv_exact(client, 1)[0]
|
||||||
recv_exact(client, methods_count)
|
methods = recv_exact(client, methods_count)
|
||||||
client.sendall(b"\x05\x00")
|
if proxy_auth_enabled():
|
||||||
|
if 2 not in methods:
|
||||||
|
client.sendall(b"\x05\xff")
|
||||||
|
return
|
||||||
|
client.sendall(b"\x05\x02")
|
||||||
|
auth_version = recv_exact(client, 1)[0]
|
||||||
|
if auth_version != 1:
|
||||||
|
client.sendall(b"\x01\x01")
|
||||||
|
return
|
||||||
|
username = recv_exact(client, recv_exact(client, 1)[0]).decode("utf-8", errors="replace")
|
||||||
|
password = recv_exact(client, recv_exact(client, 1)[0]).decode("utf-8", errors="replace")
|
||||||
|
if not check_credentials(username, password):
|
||||||
|
client.sendall(b"\x01\x01")
|
||||||
|
return
|
||||||
|
client.sendall(b"\x01\x00")
|
||||||
|
else:
|
||||||
|
client.sendall(b"\x05\x00")
|
||||||
version, command, _, address_type = recv_exact(client, 4)
|
version, command, _, address_type = recv_exact(client, 4)
|
||||||
if version != 5 or command != 1:
|
if version != 5 or command != 1:
|
||||||
client.sendall(b"\x05\x07\x00\x01\x00\x00\x00\x00\x00\x00")
|
client.sendall(b"\x05\x07\x00\x01\x00\x00\x00\x00\x00\x00")
|
||||||
@@ -223,12 +298,30 @@ def http_client(client: socket.socket, first_byte: bytes) -> None:
|
|||||||
upstream = None
|
upstream = None
|
||||||
try:
|
try:
|
||||||
header = read_http_header(client, first_byte)
|
header = read_http_header(client, first_byte)
|
||||||
|
if b"\r\n\r\n" not in header:
|
||||||
|
client.sendall(b"HTTP/1.1 400 Bad Request\r\nContent-Length: 0\r\n\r\n")
|
||||||
|
return
|
||||||
head, rest = header.split(b"\r\n\r\n", 1)
|
head, rest = header.split(b"\r\n\r\n", 1)
|
||||||
lines = head.decode("iso-8859-1", errors="replace").split("\r\n")
|
lines = head.decode("iso-8859-1", errors="replace").split("\r\n")
|
||||||
method, target, version = lines[0].split(" ", 2)
|
try:
|
||||||
|
method, target, version = lines[0].split(" ", 2)
|
||||||
|
except ValueError:
|
||||||
|
client.sendall(b"HTTP/1.1 400 Bad Request\r\nContent-Length: 0\r\n\r\n")
|
||||||
|
return
|
||||||
|
if not version.startswith("HTTP/"):
|
||||||
|
client.sendall(b"HTTP/1.1 400 Bad Request\r\nContent-Length: 0\r\n\r\n")
|
||||||
|
return
|
||||||
|
if proxy_auth_enabled():
|
||||||
|
username, password = parse_http_basic_auth(lines[1:])
|
||||||
|
if not check_credentials(username, password):
|
||||||
|
client.sendall(
|
||||||
|
b"HTTP/1.1 407 Proxy Authentication Required\r\n"
|
||||||
|
b"Proxy-Authenticate: Basic realm=\"AimiliVPN Proxy\"\r\n"
|
||||||
|
b"Content-Length: 0\r\n\r\n"
|
||||||
|
)
|
||||||
|
return
|
||||||
if method.upper() == "CONNECT":
|
if method.upper() == "CONNECT":
|
||||||
host, _, port_text = target.partition(":")
|
host, port = parse_host_port(target, 443)
|
||||||
port = parse_int(port_text) or 443
|
|
||||||
upstream = create_connection((host, port), timeout=20)
|
upstream = create_connection((host, port), timeout=20)
|
||||||
client.sendall(b"HTTP/1.1 200 Connection Established\r\n\r\n")
|
client.sendall(b"HTTP/1.1 200 Connection Established\r\n\r\n")
|
||||||
if rest:
|
if rest:
|
||||||
@@ -236,7 +329,11 @@ def http_client(client: socket.socket, first_byte: bytes) -> None:
|
|||||||
relay(client, upstream)
|
relay(client, upstream)
|
||||||
return
|
return
|
||||||
|
|
||||||
parsed = urllib.parse.urlsplit(target)
|
try:
|
||||||
|
parsed = urllib.parse.urlsplit(target)
|
||||||
|
except ValueError:
|
||||||
|
client.sendall(b"HTTP/1.1 400 Bad Request\r\nContent-Length: 0\r\n\r\n")
|
||||||
|
return
|
||||||
hostname = parsed.hostname
|
hostname = parsed.hostname
|
||||||
port = parsed.port
|
port = parsed.port
|
||||||
scheme = parsed.scheme
|
scheme = parsed.scheme
|
||||||
@@ -254,16 +351,15 @@ def http_client(client: socket.socket, first_byte: bytes) -> None:
|
|||||||
else:
|
else:
|
||||||
port = None
|
port = None
|
||||||
else:
|
else:
|
||||||
host_part, _, port_part = host_val.partition(":")
|
hostname, parsed_port = parse_host_port(host_val, 0)
|
||||||
hostname = host_part
|
port = parsed_port or None
|
||||||
port = int(port_part) if port_part.isdigit() else None
|
|
||||||
break
|
break
|
||||||
if not hostname:
|
if not hostname:
|
||||||
client.sendall(b"HTTP/1.1 400 Bad Request\r\nContent-Length: 0\r\n\r\n")
|
client.sendall(b"HTTP/1.1 400 Bad Request\r\nContent-Length: 0\r\n\r\n")
|
||||||
return
|
return
|
||||||
port = port or (443 if scheme == "https" else 80)
|
port = port or (443 if scheme == "https" else 80)
|
||||||
path = urllib.parse.urlunsplit(("", "", parsed.path or "/", parsed.query, ""))
|
path = urllib.parse.urlunsplit(("", "", parsed.path or "/", parsed.query, ""))
|
||||||
headers = [line for line in lines[1:] if not line.lower().startswith(("proxy-connection:", "connection:"))]
|
headers = [line for line in lines[1:] if not line.lower().startswith(("proxy-connection:", "connection:", "proxy-authorization:"))]
|
||||||
request = f"{method} {path} {version}\r\n" + "\r\n".join(headers) + "\r\nConnection: close\r\n\r\n"
|
request = f"{method} {path} {version}\r\n" + "\r\n".join(headers) + "\r\nConnection: close\r\n\r\n"
|
||||||
upstream = create_connection((hostname, port), timeout=20)
|
upstream = create_connection((hostname, port), timeout=20)
|
||||||
upstream.sendall(request.encode("iso-8859-1") + rest)
|
upstream.sendall(request.encode("iso-8859-1") + rest)
|
||||||
@@ -355,7 +451,21 @@ def start_proxy_server(host: str, port: int) -> None:
|
|||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
client, address = server.accept()
|
client, address = server.accept()
|
||||||
threading.Thread(target=proxy_client, args=(client, address), daemon=True).start()
|
if not proxy_connection_sem.acquire(blocking=False):
|
||||||
|
print(f"[代理限流] 当前连接数已达到上限 {MAX_PROXY_CONNECTIONS},拒绝客户端 {address}", flush=True)
|
||||||
|
try:
|
||||||
|
client.close()
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
continue
|
||||||
|
|
||||||
|
def run_client() -> None:
|
||||||
|
try:
|
||||||
|
proxy_client(client, address)
|
||||||
|
finally:
|
||||||
|
proxy_connection_sem.release()
|
||||||
|
|
||||||
|
threading.Thread(target=run_client, daemon=True).start()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[ERROR] Proxy accept failed: {e}", flush=True)
|
print(f"[ERROR] Proxy accept failed: {e}", flush=True)
|
||||||
time.sleep(0.5)
|
time.sleep(0.5)
|
||||||
|
|||||||
+693
-660
File diff suppressed because it is too large
Load Diff
+450
-134
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user