Merge upstream/python_testing

This commit is contained in:
PK3NZO
2026-04-23 15:06:20 +03:30
5 changed files with 148 additions and 95 deletions
+23 -1
View File
@@ -16,6 +16,28 @@ For the latest news, releases, and project updates, follow our Telegram channel:
--- ---
### If you like this project, please support it by starring it on GitHub (⭐). It helps the project get discovered.
---
### Optional Financial Support 💸
- TON network:
`masterking32.ton`
- EVM-compatible networks (ETH and compatible chains):
`0x517f07305D6ED781A089322B6cD93d1461bF8652`
- TRC20 network (TRON):
`TLApdY8APWkFHHoxebxGY8JhMeChiETqFH`
Every contribution and every piece of feedback is appreciated. Support directly helps ongoing development and improvement.
---
## Disclaimer ## Disclaimer
MasterHttpRelayVPN is provided for educational, testing, and research purposes only. MasterHttpRelayVPN is provided for educational, testing, and research purposes only.
@@ -287,7 +309,7 @@ Install everything from [`requirements.txt`](requirements.txt). All listed packa
| `h2` | HTTP/2 multiplexing to the Apps Script relay (significantly faster) | | `h2` | HTTP/2 multiplexing to the Apps Script relay (significantly faster) |
| `brotli` | Decompression of `Content-Encoding: br` responses | | `brotli` | Decompression of `Content-Encoding: br` responses |
| `zstandard` | Decompression of `Content-Encoding: zstd` responses | | `zstandard` | Decompression of `Content-Encoding: zstd` responses |
| `netifaces` | Better network interface detection for LAN sharing (fallback available without it) |
### Load Balancing ### Load Balancing
+22 -1
View File
@@ -14,6 +14,28 @@
--- ---
### اگر از پروژه راضی‌اید، با دادن ستاره (⭐) در گیت‌هاب از ما حمایت کنید — این کار به دیده‌شدن پروژه کمک می‌کند.
---
### حمایت مالی (اختیاری) 💸
- شبکه TON:
`masterking32.ton`
- آدرس روی شبکه‌های EVM (ETH و سازگارها):
`0x517f07305D6ED781A089322B6cD93d1461bF8652`
- شبکه TRC20 (TRON):
`TLApdY8APWkFHHoxebxGY8JhMeChiETqFH`
از هر نوع حمایت و بازخورد شما سپاسگزاریم — کمک‌ها برای توسعه و بهبود پروژه بسیار ارزشمند است.
---
## سلب مسئولیت ## سلب مسئولیت
پروژه MasterHttpRelayVPN فقط برای اهداف آموزشی، تست و پژوهش ارائه شده است. پروژه MasterHttpRelayVPN فقط برای اهداف آموزشی، تست و پژوهش ارائه شده است.
@@ -235,7 +257,6 @@ json
| `h2` | ارتباط HTTP/2 با رله Apps Script (به‌طور محسوسی سریع‌تر) | | `h2` | ارتباط HTTP/2 با رله Apps Script (به‌طور محسوسی سریع‌تر) |
| `brotli` | پشتیبانی از فشرده‌سازی `Content-Encoding: br` | | `brotli` | پشتیبانی از فشرده‌سازی `Content-Encoding: br` |
| `zstandard` | پشتیبانی از فشرده‌سازی `Content-Encoding: zstd` | | `zstandard` | پشتیبانی از فشرده‌سازی `Content-Encoding: zstd` |
| `netifaces` | تشخیص بهتر اینترفیس‌های شبکه برای اشتراک‌گذاری LAN (در صورت نبود آن، حالت جایگزین در دسترس است) |
### استفاده از چند Script ID ### استفاده از چند Script ID
+2 -2
View File
@@ -13,5 +13,5 @@ brotli>=1.1.0
# Optional: Zstandard decompression (some CDNs now serve `zstd`) # Optional: Zstandard decompression (some CDNs now serve `zstd`)
zstandard>=0.22.0 zstandard>=0.22.0
# Optional: Better network interface detection for LAN sharing # LAN interface detection now uses only the Python standard library
netifaces>=0.11.0 # (works on Windows, Linux, macOS, Android/Termux without a C compiler).
+82 -79
View File
@@ -1,101 +1,103 @@
""" """
LAN utilities for detecting network interfaces and IP addresses. LAN utilities for detecting network interfaces and IPv4 addresses.
Provides functionality to enumerate local network interfaces and their Provides functionality to enumerate local IPv4 addresses for LAN proxy
associated IP addresses for LAN proxy sharing. sharing. IPv6 is intentionally not reported — this project only exposes
the proxy over IPv4 LANs, which is what every consumer router and
phone/desktop client actually uses.
Implementation notes
--------------------
This module relies only on the Python standard library so it works
out-of-the-box on every supported OS (Windows, Linux, macOS,
Android/Termux, *BSD) without requiring a C compiler or native build
tools (previous versions depended on ``netifaces``, which needs
"Microsoft Visual C++ 14.0 or greater" on Windows and was a frequent
install blocker for users on slow connections).
Strategy (in order):
1. "UDP connect trick" to reliably discover the primary outbound
IPv4 address on any OS.
2. ``socket.getaddrinfo(hostname, AF_INET)`` to enumerate any additional
IPv4 addresses bound to the host (covers multi-homed machines).
""" """
import ipaddress import ipaddress
import logging import logging
import socket import socket
from typing import Dict, List, Optional from typing import Dict, List, Optional, Set
log = logging.getLogger("LAN") log = logging.getLogger("LAN")
# ---------------------------------------------------------------------------
# Primary-IP discovery (UDP connect trick)
# ---------------------------------------------------------------------------
def _primary_ipv4() -> Optional[str]:
"""
Return the primary local IPv4 the OS would use for outbound traffic.
Uses a connected UDP socket which does *not* actually send packets —
the kernel just picks the source address from its routing table.
Works identically on Windows, Linux, macOS, and Android.
"""
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
s.settimeout(0.5)
# TEST-NET-1 address, port is arbitrary; no packet is sent for UDP connect().
s.connect(('192.0.2.1', 80))
return s.getsockname()[0]
except OSError:
return None
finally:
s.close()
# ---------------------------------------------------------------------------
# Public API
# ---------------------------------------------------------------------------
def get_network_interfaces() -> Dict[str, List[str]]: def get_network_interfaces() -> Dict[str, List[str]]:
""" """
Get all network interfaces and their associated IP addresses. Get network interfaces and their associated non-loopback IPv4 addresses.
Returns a dictionary mapping interface names to lists of IP addresses
(both IPv4 and IPv6). Only includes interfaces with valid IP addresses
that are not loopback.
Returns: Returns:
Dict[str, List[str]]: Interface name -> list of IP addresses Dict[str, List[str]]: Interface label -> list of IPv4 addresses.
Labels are best-effort synthetic names such as ``"primary"``
and ``"host"``.
""" """
interfaces = {} interfaces: Dict[str, List[str]] = {}
seen_ips: Set[str] = set()
def _add(label: str, ip: Optional[str]) -> None:
if not ip or ip in seen_ips:
return
if ip.startswith('127.'):
return
seen_ips.add(ip)
interfaces.setdefault(label, []).append(ip)
# 1) Primary outbound IPv4 (most reliable, cross-platform).
_add('primary', _primary_ipv4())
# 2) Enumerate via hostname resolution (picks up multi-homed hosts).
try: try:
import netifaces
for iface in netifaces.interfaces():
addrs = netifaces.ifaddresses(iface)
ips = []
# IPv4 addresses
if netifaces.AF_INET in addrs:
for addr in addrs[netifaces.AF_INET]:
ip = addr.get('addr')
if ip and not ip.startswith('127.'):
ips.append(ip)
# IPv6 addresses (without scope)
if netifaces.AF_INET6 in addrs:
for addr in addrs[netifaces.AF_INET6]:
ip = addr.get('addr')
if ip and not ip.startswith('::1') and not '%' in ip:
# Remove scope if present
ips.append(ip.split('%')[0])
if ips:
interfaces[iface] = ips
except ImportError:
# Fallback to socket method for basic detection
log.debug("netifaces not available, using socket fallback")
interfaces = _get_interfaces_socket_fallback()
return interfaces
def _get_interfaces_socket_fallback() -> Dict[str, List[str]]:
"""
Fallback method to get network interfaces using socket.
This is less comprehensive than netifaces but works without extra dependencies.
"""
interfaces = {}
try:
# Get hostname and try to resolve to IPs
hostname = socket.gethostname() hostname = socket.gethostname()
try: except OSError:
# Get IPv4 addresses hostname = ''
ipv4_info = socket.getaddrinfo(hostname, None, socket.AF_INET)
ipv4_addrs = [info[4][0] for info in ipv4_info if not info[4][0].startswith('127.')]
if ipv4_addrs:
interfaces['primary'] = list(set(ipv4_addrs)) # Remove duplicates
except socket.gaierror:
pass
if hostname:
try: try:
# Get IPv6 addresses for info in socket.getaddrinfo(hostname, None, socket.AF_INET):
ipv6_info = socket.getaddrinfo(hostname, None, socket.AF_INET6) _add('host', info[4][0])
ipv6_addrs = [] except (socket.gaierror, OSError):
for info in ipv6_info:
ip = info[4][0]
if not ip.startswith('::1') and not '%' in ip:
ipv6_addrs.append(ip.split('%')[0])
if ipv6_addrs:
interfaces['primary_ipv6'] = list(set(ipv6_addrs))
except socket.gaierror:
pass pass
except Exception as e:
log.debug("Socket fallback failed: %s", e)
return interfaces return interfaces
def get_lan_ips(port: int = 8085) -> List[str]: def get_lan_ips(port: int = 8085) -> List[str]:
""" """
Get list of LAN-accessible proxy addresses. Get list of LAN-accessible proxy addresses (IPv4 only).
Returns a list of IP:port combinations that can be used to access Returns a list of IP:port combinations that can be used to access
the proxy from other devices on the local network. the proxy from other devices on the local network.
@@ -107,21 +109,22 @@ def get_lan_ips(port: int = 8085) -> List[str]:
List[str]: List of "IP:port" strings for LAN access List[str]: List of "IP:port" strings for LAN access
""" """
interfaces = get_network_interfaces() interfaces = get_network_interfaces()
lan_addresses = [] lan_addresses: List[str] = []
for iface_ips in interfaces.values(): for iface_ips in interfaces.values():
for ip in iface_ips: for ip in iface_ips:
try: try:
# Validate IP and check if it's a private address addr = ipaddress.IPv4Address(ip)
addr = ipaddress.ip_address(ip) except (ValueError, ipaddress.AddressValueError):
if addr.is_private or addr.is_link_local:
lan_addresses.append(f"{ip}:{port}")
except ValueError:
continue continue
if addr.is_loopback or addr.is_unspecified:
continue
if addr.is_private or addr.is_link_local:
lan_addresses.append(f"{ip}:{port}")
# Remove duplicates while preserving order # Remove duplicates while preserving order.
seen = set() seen: Set[str] = set()
unique_addresses = [] unique_addresses: List[str] = []
for addr in lan_addresses: for addr in lan_addresses:
if addr not in seen: if addr not in seen:
seen.add(addr) seen.add(addr)
+19 -12
View File
@@ -661,12 +661,17 @@ class ProxyServer:
# • port 443 → MITM + relay through Apps Script # • port 443 → MITM + relay through Apps Script
# • port 80 → plain-HTTP relay through Apps Script # • port 80 → plain-HTTP relay through Apps Script
# • other → give up (non-HTTP; can't be relayed) # • other → give up (non-HTTP; can't be relayed)
# We use a shorter connect timeout for IP literals (4 s) because
# when the route is DPI-dropped, waiting longer doesn't help and
# clients like Telegram speed up DC-rotation when we fail fast.
# We remember per-IP failures for a short while so subsequent # We remember per-IP failures for a short while so subsequent
# connects skip the doomed direct attempt. # connects skip the doomed direct attempt.
if _is_ip_literal(host): if _is_ip_literal(host):
if not self._direct_temporarily_disabled(host): if not self._direct_temporarily_disabled(host):
log.info("Direct tunnel → %s:%d (IP literal)", host, port) log.info("Direct tunnel → %s:%d (IP literal)", host, port)
ok = await self._do_direct_tunnel(host, port, reader, writer) ok = await self._do_direct_tunnel(
host, port, reader, writer, timeout=4.0,
)
if ok: if ok:
return return
self._remember_direct_failure(host, ttl=300) self._remember_direct_failure(host, ttl=300)
@@ -900,7 +905,8 @@ class ProxyServer:
async def _do_direct_tunnel(self, host: str, port: int, async def _do_direct_tunnel(self, host: str, port: int,
reader: asyncio.StreamReader, reader: asyncio.StreamReader,
writer: asyncio.StreamWriter, writer: asyncio.StreamWriter,
connect_ip: str | None = None): connect_ip: str | None = None,
timeout: float | None = None):
"""Pipe raw TLS bytes directly to the target server. """Pipe raw TLS bytes directly to the target server.
connect_ip overrides DNS: the TCP connection goes to that IP connect_ip overrides DNS: the TCP connection goes to that IP
@@ -910,9 +916,12 @@ class ProxyServer:
normal edge instead of being forced onto the fronting IP. normal edge instead of being forced onto the fronting IP.
""" """
target_ip = connect_ip or host target_ip = connect_ip or host
effective_timeout = (
self._tcp_connect_timeout if timeout is None else float(timeout)
)
try: try:
r_remote, w_remote = await self._open_tcp_connection( r_remote, w_remote = await self._open_tcp_connection(
target_ip, port, timeout=self._tcp_connect_timeout, target_ip, port, timeout=effective_timeout,
) )
except Exception as e: except Exception as e:
log.error("Direct tunnel connect failed (%s via %s): %s", log.error("Direct tunnel connect failed (%s via %s): %s",
@@ -1044,17 +1053,15 @@ class ProxyServer:
# • Telegram Desktop / MTProto over port 443 sends obfuscated # • Telegram Desktop / MTProto over port 443 sends obfuscated
# non-TLS bytes — we literally cannot decrypt these, and # non-TLS bytes — we literally cannot decrypt these, and
# since the target IP is blocked we can't direct-tunnel # since the target IP is blocked we can't direct-tunnel
# either. The only workaround is to configure Telegram as # either. Telegram will rotate to another DC on its own;
# an HTTP proxy (not SOCKS5), so it sends hostnames our # failing fast here lets that happen sooner.
# SNI-rewrite path can handle.
# • Client CONNECTs but never speaks TLS (some probes). # • Client CONNECTs but never speaks TLS (some probes).
if _is_ip_literal(host) and port == 443: if _is_ip_literal(host) and port == 443:
log.warning( log.info(
"MITM TLS handshake failed for %s:%d (%s). " "Non-TLS traffic on %s:%d (likely Telegram MTProto / "
"Likely non-TLS traffic (e.g. Telegram MTProto over " "obfuscated protocol). This DC appears blocked; the "
"SOCKS5). Cannot relay raw TCP to a blocked IP — " "client should rotate to another endpoint shortly.",
"use the HTTP proxy instead so hostnames are preserved.", host, port,
host, port, e,
) )
elif port != 443: elif port != 443:
log.debug( log.debug(