feat: WS-fronted profiles to bypass TLS handshake filtering

- add _build_ws_link: rebuild loopback panel WS links into public
  vless://uuid@host:port?type=ws&security=none links
- matcher supports per-inbound ws block (match on xray-side port)
- serves SE Sweden-WS (via Caddy :80) and NL Netherlands-WS (:80)
This commit is contained in:
SashegDev
2026-09-30 04:37:48 +00:00
parent 65cfec7137
commit 029ea7031c
+42
View File
@@ -542,6 +542,39 @@ def _link_port(link: str) -> Optional[int]:
except Exception:
return None
def _build_ws_link(panel_link: str, inbound: dict) -> Optional[str]:
"""Rebuild a panel WS link (often with localhost host) into a public client link.
Panel generates e.g. vless://uuid@127.0.0.1:10085?... for loopback-bound
WS inbounds; clients need the public front instead:
vless://uuid@<ws.host>:<ws.port>?type=ws&security=none&host=<ws.host>&path=...
Triggered by the optional "ws" block in servers.conf:
{"host": "ws.example.com", "port": 80, "xray_port": 10085}.
Returns None on parse error.
"""
import urllib.parse
ws = inbound.get("ws") or {}
if not ws.get("host") or not ws.get("port"):
return panel_link
try:
parts = urllib.parse.urlparse(panel_link.split("#")[0])
userinfo = parts.username or ""
if not userinfo:
return None
query = dict(urllib.parse.parse_qsl(parts.query, keep_blank_values=True))
query["type"] = "ws"
query["security"] = "none"
query["host"] = ws["host"]
if "path" not in query:
return None
host = ws["host"]
port = int(ws["port"])
netloc = f"{userinfo}@{host}:{port}"
# hysteria-style links carry passwords; vless carries uuid only
return f"{parts.scheme}://{netloc}?{urllib.parse.urlencode(query)}"
except Exception:
return None
def _build_hysteria_link(panel_link: str, sub_id: str, srv: dict) -> Optional[str]:
"""Rebuild panel hysteria2://AUTH@host:port link into a working client link.
@@ -1016,6 +1049,11 @@ async def get_subscription(request: Request, subscription_id: str, format: str =
ib_protocol = _get_inbound_protocol(inbound)
ib_port = inbound.get("port")
# WS-fronted inbounds: panel links carry the loopback xray port;
# match on it, the public link is rebuilt below.
ws_cfg = inbound.get("ws") or {}
if ws_cfg.get("xray_port"):
ib_port = ws_cfg["xray_port"]
matched_link = None
fallback_link = None
for candidate in links:
@@ -1045,6 +1083,10 @@ async def get_subscription(request: Request, subscription_id: str, format: str =
link = matched_link
if ib_protocol == "hysteria":
link = _build_hysteria_link(link, subscription_id, srv) or link
if ws_cfg:
link = _build_ws_link(link, inbound)
if link is None:
continue
clean_link = link.split('#')[0]
if clean_link in seen_links:
continue