mirror of
https://github.com/masterking32/MasterHttpRelayVPN.git
synced 2026-05-17 21:24:37 +03:00
Removed netifaces for better compatibility
This commit is contained in:
@@ -278,7 +278,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
|
||||||
|
|
||||||
|
|||||||
@@ -226,7 +226,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
@@ -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).
|
||||||
|
|||||||
+88
-73
@@ -3,92 +3,104 @@ LAN utilities for detecting network interfaces and IP addresses.
|
|||||||
|
|
||||||
Provides functionality to enumerate local network interfaces and their
|
Provides functionality to enumerate local network interfaces and their
|
||||||
associated IP addresses for LAN proxy sharing.
|
associated IP addresses for LAN proxy sharing.
|
||||||
|
|
||||||
|
Implementation notes
|
||||||
|
--------------------
|
||||||
|
This module intentionally relies only on the Python standard library so
|
||||||
|
that 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 and IPv6 addresses on any OS.
|
||||||
|
2. ``socket.getaddrinfo(hostname, ...)`` to enumerate additional
|
||||||
|
addresses bound to the host (covers multi-homed machines).
|
||||||
|
|
||||||
|
These two steps together cover every real-world LAN scenario on
|
||||||
|
Windows, Linux, macOS, Android/Termux and *BSD. (We intentionally do
|
||||||
|
*not* try to map per-interface names to IPs via the stdlib — that is
|
||||||
|
not portable and, on Windows, triggers 30 s DNS timeouts.)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
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_ip(family: int, probe_addr: str) -> Optional[str]:
|
||||||
|
"""
|
||||||
|
Return the primary local IP the OS would use to reach ``probe_addr``.
|
||||||
|
|
||||||
|
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(family, socket.SOCK_DGRAM)
|
||||||
|
try:
|
||||||
|
s.settimeout(0.5)
|
||||||
|
# Port 80 is arbitrary; no packet is sent for UDP connect().
|
||||||
|
s.connect((probe_addr, 80))
|
||||||
|
ip = s.getsockname()[0]
|
||||||
|
if family == socket.AF_INET6:
|
||||||
|
ip = ip.split('%', 1)[0] # strip zone id
|
||||||
|
return ip
|
||||||
|
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 IP 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 IP addresses.
|
||||||
|
Interface names are best-effort; on some platforms we fall back
|
||||||
|
to synthetic labels such as ``"primary"`` / ``"primary_ipv6"``.
|
||||||
"""
|
"""
|
||||||
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.') or ip == '::1':
|
||||||
|
return
|
||||||
|
seen_ips.add(ip)
|
||||||
|
interfaces.setdefault(label, []).append(ip)
|
||||||
|
|
||||||
|
# 1) Primary outbound IPs (most reliable, cross-platform).
|
||||||
|
_add('primary', _primary_ip(socket.AF_INET, '192.0.2.1')) # TEST-NET-1
|
||||||
|
_add('primary_ipv6', _primary_ip(socket.AF_INET6, '2001:db8::1')) # doc prefix
|
||||||
|
|
||||||
|
# 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
|
|
||||||
|
|
||||||
|
for family, label in ((socket.AF_INET, 'host'), (socket.AF_INET6, 'host_ipv6')):
|
||||||
try:
|
try:
|
||||||
# Get IPv6 addresses
|
for info in socket.getaddrinfo(hostname, None, family):
|
||||||
ipv6_info = socket.getaddrinfo(hostname, None, socket.AF_INET6)
|
|
||||||
ipv6_addrs = []
|
|
||||||
for info in ipv6_info:
|
|
||||||
ip = info[4][0]
|
ip = info[4][0]
|
||||||
if not ip.startswith('::1') and not '%' in ip:
|
if family == socket.AF_INET6:
|
||||||
ipv6_addrs.append(ip.split('%')[0])
|
ip = ip.split('%', 1)[0]
|
||||||
if ipv6_addrs:
|
_add(label, ip)
|
||||||
interfaces['primary_ipv6'] = list(set(ipv6_addrs))
|
except (socket.gaierror, OSError):
|
||||||
except socket.gaierror:
|
continue
|
||||||
pass
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
log.debug("Socket fallback failed: %s", e)
|
|
||||||
|
|
||||||
return interfaces
|
return interfaces
|
||||||
|
|
||||||
@@ -107,21 +119,24 @@ 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.ip_address(ip)
|
addr = ipaddress.ip_address(ip)
|
||||||
if addr.is_private or addr.is_link_local:
|
|
||||||
lan_addresses.append(f"{ip}:{port}")
|
|
||||||
except ValueError:
|
except ValueError:
|
||||||
continue
|
continue
|
||||||
|
if addr.is_loopback or addr.is_unspecified:
|
||||||
|
continue
|
||||||
|
# Include private, link-local, and unique-local (IPv6 fc00::/7) ranges.
|
||||||
|
if addr.is_private or addr.is_link_local:
|
||||||
|
bracket = f"[{ip}]" if isinstance(addr, ipaddress.IPv6Address) else ip
|
||||||
|
lan_addresses.append(f"{bracket}:{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)
|
||||||
|
|||||||
Reference in New Issue
Block a user