diff --git a/dae/Makefile b/dae/Makefile
index a413a6c9..a1b77a3f 100644
--- a/dae/Makefile
+++ b/dae/Makefile
@@ -6,12 +6,12 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=dae
PKG_VERSION:=2026.11.09
-PKG_RELEASE:=1
+PKG_RELEASE:=2
-PKG_SOURCE:=dae-src-2026.11.09-45df6d7de7ee.tar.gz
+PKG_SOURCE:=dae-src-2026.11.09-8b744ffb65ee.tar.gz
PKG_SOURCE_URL:=https://github.com/kenzok8/openwrt-daede/releases/download/dae-src
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
-PKG_HASH:=45df6d7de7ee262cb8b22a377037dc9c1498ffa282b6be4364bee3fbecdbcc55
+PKG_HASH:=8b744ffb65eedf46e9f3cf035e757bda30547d2dafed522eb3154bd4e509e146
PKG_LICENSE:=AGPL-3.0-only
PKG_LICENSE_FILE:=LICENSE
diff --git a/daed/Makefile b/daed/Makefile
index 7ac84429..7e7971be 100644
--- a/daed/Makefile
+++ b/daed/Makefile
@@ -6,12 +6,12 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=daed
PKG_VERSION:=2026.11.09
-PKG_RELEASE:=1
+PKG_RELEASE:=2
-PKG_SOURCE:=daed-src-2026.11.09-82f4f6af67da.tar.gz
+PKG_SOURCE:=daed-src-2026.11.09-a25f8d76d474.tar.gz
PKG_SOURCE_URL:=https://github.com/kenzok8/openwrt-daede/releases/download/daed-src
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
-PKG_HASH:=82f4f6af67da0069958810d484e2957a4d8ab48b078d71e27e62d96c5dcd4faa
+PKG_HASH:=a25f8d76d4745a114f52f7b80d0636aad7e4a9be06488db8e2753dc119547dd0
PKG_LICENSE:=AGPL-3.0-only MIT
PKG_LICENSE_FILES:=LICENSE wing/LICENSE
diff --git a/luci-app-passwall/Makefile b/luci-app-passwall/Makefile
index 0a0d6b22..0cf145e9 100644
--- a/luci-app-passwall/Makefile
+++ b/luci-app-passwall/Makefile
@@ -7,7 +7,7 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-passwall
-PKG_VERSION:=26.11.3
+PKG_VERSION:=26.11.11
PKG_RELEASE:=1
PKG_PO_VERSION:=$(PKG_VERSION)
diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/client/node_subscribe.lua b/luci-app-passwall/luasrc/model/cbi/passwall/client/node_subscribe.lua
index 8762ace1..4cb05527 100644
--- a/luci-app-passwall/luasrc/model/cbi/passwall/client/node_subscribe.lua
+++ b/luci-app-passwall/luasrc/model/cbi/passwall/client/node_subscribe.lua
@@ -10,6 +10,7 @@ local trojan_type = {}
local vmess_type = {}
local vless_type = {}
local hysteria2_type = {}
+local other_type = {}
local xray_version = api.get_app_version("xray")
if has_ss_rust then
local s = "shadowsocks-rust"
@@ -22,6 +23,7 @@ if has_singbox then
table.insert(vmess_type, s)
table.insert(vless_type, s)
table.insert(hysteria2_type, s)
+ table.insert(other_type, s)
end
if has_xray then
local s = "xray"
@@ -29,6 +31,7 @@ if has_xray then
table.insert(ss_type, s)
table.insert(vmess_type, s)
table.insert(vless_type, s)
+ table.insert(other_type, s)
if api.compare_versions(xray_version, ">=", "26.1.13") then
table.insert(hysteria2_type, s)
end
@@ -100,7 +103,15 @@ if #hysteria2_type > 0 then
end
end
-if #ss_type > 0 or #trojan_type > 0 or #vmess_type > 0 or #vless_type > 0 or #hysteria2_type > 0 then
+if #other_type > 0 then
+ o = s:option(ListValue, "other_type", translatef("%s Node Use Type", translate("Other")))
+ o:value("", translate("Auto"))
+ for key, value in pairs(other_type) do
+ o:value(value)
+ end
+end
+
+if #ss_type > 0 or #trojan_type > 0 or #vmess_type > 0 or #vless_type > 0 or #hysteria2_type > 0 or #other_type > 0 then
o.description = string.format("%s",
translate("The configured type also applies to the core specified when manually importing nodes."))
end
diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/client/node_subscribe_config.lua b/luci-app-passwall/luasrc/model/cbi/passwall/client/node_subscribe_config.lua
index f504e826..531349f5 100644
--- a/luci-app-passwall/luasrc/model/cbi/passwall/client/node_subscribe_config.lua
+++ b/luci-app-passwall/luasrc/model/cbi/passwall/client/node_subscribe_config.lua
@@ -23,6 +23,7 @@ local trojan_type = {}
local vmess_type = {}
local vless_type = {}
local hysteria2_type = {}
+local other_type = {}
local xray_version = api.get_app_version("xray")
if has_ss_rust then
local s = "shadowsocks-rust"
@@ -35,6 +36,7 @@ if has_singbox then
table.insert(vmess_type, s)
table.insert(vless_type, s)
table.insert(hysteria2_type, s)
+ table.insert(other_type, s)
end
if has_xray then
local s = "xray"
@@ -42,6 +44,7 @@ if has_xray then
table.insert(ss_type, s)
table.insert(vmess_type, s)
table.insert(vless_type, s)
+ table.insert(other_type, s)
if api.compare_versions(xray_version, ">=", "26.1.13") then
table.insert(hysteria2_type, s)
end
@@ -222,7 +225,18 @@ if #hysteria2_type > 0 then
for key, value in pairs(hysteria2_type) do
o:value(value)
end
+end
+if #other_type > 0 then
+ o = s:option(ListValue, "other_type", translatef("%s Node Use Type", translate("Other")))
+ o.default = "global"
+ o:value("global", translate("Use global config"))
+ for key, value in pairs(other_type) do
+ o:value(value)
+ end
+end
+
+if #hysteria2_type > 0 then
o = s:option(Value, "hysteria_up_mbps", "Hy/Hy2 " .. translate("Max upload Mbps"))
o.datatype = "uinteger"
diff --git a/luci-app-passwall/luasrc/model/cbi/passwall/client/type/1_sing-box.lua b/luci-app-passwall/luasrc/model/cbi/passwall/client/type/1_sing-box.lua
index c36f3073..5b9d08d8 100644
--- a/luci-app-passwall/luasrc/model/cbi/passwall/client/type/1_sing-box.lua
+++ b/luci-app-passwall/luasrc/model/cbi/passwall/client/type/1_sing-box.lua
@@ -706,6 +706,10 @@ if singbox_tags:find("with_wireguard") then
o = s:option(Value, "wireguard_reserved", translate("Reserved"), translate("Decimal numbers separated by \",\" or Base64-encoded strings."))
o:depends({ protocol = "wireguard" })
+
+ o = s:option(Value, "wireguard_keepAlive", translate("Keep Alive"))
+ o.default = "0"
+ o:depends({ protocol = "wireguard" })
end
-- [[ TCP部分(模拟) ]]--
diff --git a/luci-app-passwall/luasrc/passwall/util_sing-box.lua b/luci-app-passwall/luasrc/passwall/util_sing-box.lua
index 90a6df4f..075b2c3b 100644
--- a/luci-app-passwall/luasrc/passwall/util_sing-box.lua
+++ b/luci-app-passwall/luasrc/passwall/util_sing-box.lua
@@ -507,6 +507,7 @@ function gen_outbound(flag, node, tag, proxy_table)
pre_shared_key = node.wireguard_preSharedKey,
reserved = node.wireguard_reserved,
mtu = tonumber(node.wireguard_mtu),
+ persistent_keepalive_interval = tonumber(node.wireguard_keepAlive),
}
end
@@ -2438,7 +2439,8 @@ function gen_config(var)
port = value.server_port,
public_key = value.peer_public_key,
pre_shared_key = value.pre_shared_key,
- allowed_ips = {"0.0.0.0/0"},
+ allowed_ips = {"0.0.0.0/0", "::/0"},
+ persistent_keepalive_interval = value.persistent_keepalive_interval,
reserved = value.reserved
}
},
diff --git a/luci-app-passwall/luasrc/passwall/util_xray.lua b/luci-app-passwall/luasrc/passwall/util_xray.lua
index 50af4178..5cf6a452 100644
--- a/luci-app-passwall/luasrc/passwall/util_xray.lua
+++ b/luci-app-passwall/luasrc/passwall/util_xray.lua
@@ -12,7 +12,7 @@ local GLOBAL = {
local xray_version = api.get_app_version("xray")
-local xray_min_version = "26.7.11"
+local xray_min_version = "26.9.9"
local function get_domain_excluded()
local path = string.format("/etc/%s/rules/domains_excluded", api.c_config)
@@ -355,6 +355,30 @@ function gen_outbound(flag, node, tag, proxy_table)
}
}
udp[#udp+1] = r
+ elseif node.hysteria2_hop and node.hysteria2_hop ~= "" then
+ udp[#udp+1] = {
+ type = "udphop",
+ settings = {
+ mode = "intervallocal,intervalremote",
+ remotePorts = string.gsub(node.hysteria2_hop, ":", "-"),
+ interval = (function(v)
+ if not v then return 30 end
+ if v:find("-", 1, true) then
+ local min, max = v:match("^(%d+)%-(%d+)$")
+ min = tonumber(min)
+ max = tonumber(max)
+ if min and max then
+ min = (min >= 5) and min or 5
+ max = (max >= min) and max or min
+ return min .. "-" .. max
+ end
+ return 30
+ end
+ v = tonumber((v or "30"):match("^%d+"))
+ return (v and v >= 5) and v or 30
+ end)(node.hysteria2_hop_interval)
+ }
+ }
end
finalmask.udp = udp
local up = tonumber(node.hysteria2_up_mbps) or 0
@@ -363,25 +387,6 @@ function gen_outbound(flag, node, tag, proxy_table)
congestion = (up <= 0 and down <= 0) and "bbr" or "brutal",
brutalUp = up > 0 and (up .. "mbps") or nil,
brutalDown = down > 0 and (down .. "mbps") or nil,
- udpHop = (node.hysteria2_hop) and {
- ports = string.gsub(node.hysteria2_hop, ":", "-"),
- interval = (function(v)
- if not v then return 30 end
- if v:find("-", 1, true) then
- local min, max = v:match("^(%d+)%-(%d+)$")
- min = tonumber(min)
- max = tonumber(max)
- if min and max then
- min = (min >= 5) and min or 5
- max = (max >= min) and max or min
- return min .. "-" .. max
- end
- return 30
- end
- v = tonumber((v or "30"):match("^%d+"))
- return (v and v >= 5) and v or 30
- end)(node.hysteria2_hop_interval)
- } or nil,
maxIdleTimeout = (function(t)
t = tonumber(tostring(t or "30"):match("^%d+"))
return (t and t >= 4 and t <= 120) and t or 30
@@ -431,7 +436,7 @@ function gen_outbound(flag, node, tag, proxy_table)
peers = (node.protocol == "wireguard") and {
{
publicKey = node.wireguard_public_key,
- endpoint = node.address .. ":" .. node.port,
+ endpoint = (api.is_ipv6(node.address) and ("[" .. node.address .. "]") or node.address) .. ":" .. node.port,
preSharedKey = node.wireguard_preSharedKey,
keepAlive = node.wireguard_keepAlive and tonumber(node.wireguard_keepAlive) or nil
}
diff --git a/luci-app-passwall/luasrc/view/passwall/node_config/link_share_man.htm b/luci-app-passwall/luasrc/view/passwall/node_config/link_share_man.htm
index 51513554..6918a98f 100644
--- a/luci-app-passwall/luasrc/view/passwall/node_config/link_share_man.htm
+++ b/luci-app-passwall/luasrc/view/passwall/node_config/link_share_man.htm
@@ -14,6 +14,7 @@ local trojan_type = api.get_core("trojan_type", {{has_singbox,"sing-box"},{has_x
local vmess_type = api.get_core("vmess_type", {{has_xray,"xray"},{has_singbox,"sing-box"}})
local vless_type = api.get_core("vless_type", {{has_xray,"xray"},{has_singbox,"sing-box"}})
local hysteria2_type = api.get_core("hysteria2_type", {{has_hysteria2,"hysteria2"},{has_singbox,"sing-box"}, {has_xray,"xray"}})
+local other_type = api.get_core("other_type", {{has_singbox,"sing-box"},{has_xray,"xray"}})
local current_node = map:get(section)
-%>
@@ -27,6 +28,7 @@ local current_node = map:get(section)
let vmess_type = "<%=vmess_type%>"
let vless_type = "<%=vless_type%>"
let hysteria2_type = "<%=hysteria2_type%>"
+ let other_type = "<%=other_type%>"
function padright(str, cnt, pad) {
return str + Array(cnt + 1).join(pad);
@@ -191,6 +193,8 @@ local current_node = map:get(section)
dom_prefix = "xray_"
} else if (v_type === "sing-box") {
dom_prefix = "singbox_"
+ } else if (v_type === "Socks") {
+ dom_prefix = "socks_"
}
var _address = ""
if (dom_prefix && dom_prefix != null) {
@@ -741,6 +745,74 @@ local current_node = map:get(section)
params = params.substring(1);
}
url += params;
+ } else if ((v_type === "Xray" || v_type === "sing-box") && opt.get(dom_prefix + "protocol").value === "wireguard") {
+ protocol = "wireguard";
+ var v_password = opt.get(dom_prefix + "wireguard_secret_key");
+ var v_port = opt.get(dom_prefix + "port");
+ url = encodeURIComponent(v_password.value) +
+ "@" + _address +
+ ":" + v_port.value + "?";
+
+ var params = "";
+ params += opt.query("publickey", dom_prefix + "wireguard_public_key");
+ params += opt.query("presharedkey", dom_prefix + "wireguard_preSharedKey");
+ var addresses = opt.getlist(dom_prefix + "wireguard_local_address").map(value => value.trim()).filter(Boolean);
+ params += "&address=" + encodeURIComponent(addresses.join(","));
+ params += opt.query("reserved", dom_prefix + "wireguard_reserved");
+ params += opt.query("mtu", dom_prefix + "wireguard_mtu");
+ params += opt.query("persistent-keepalive", dom_prefix + "wireguard_keepAlive");
+ if (v_type === "Xray" && opt.get(dom_prefix + "use_finalmask")?.checked) {
+ params += opt.query("fm", dom_prefix + "finalmask");
+ }
+ params += "#" + encodeURIComponent(v_alias.value);
+ if (params[0] == "&") {
+ params = params.substring(1);
+ }
+ url += params;
+ } else if (v_type === "Socks" || ((v_type === "sing-box" || v_type === "Xray") && opt.get(dom_prefix + "protocol").value === "socks")) {
+ protocol = "socks";
+ var v_username = opt.get(dom_prefix + "username");
+ var v_password = opt.get(dom_prefix + "password");
+ var v_port = opt.get(dom_prefix + "port");
+ url = _address + ":" + v_port.value;
+ if (v_username.value || v_password.value) {
+ url = b64encutf8safe(v_username.value + ":" + v_password.value) + "@" + url;
+ }
+ var params = "";
+ params += opt.query("uot", dom_prefix + "uot");
+ params += opt.query("tfo", dom_prefix + "tcp_fast_open");
+ if (params) {
+ url += "?" + params.substring(1);
+ }
+ url += "#" + encodeURIComponent(v_alias.value);
+ } else if ((v_type === "sing-box" || v_type === "Xray") && opt.get(dom_prefix + "protocol").value === "http") {
+ protocol = opt.get(dom_prefix + "tls").checked ? "https" : "http";
+ var v_username = opt.get(dom_prefix + "username");
+ var v_password = opt.get(dom_prefix + "password");
+ var v_port = opt.get(dom_prefix + "port");
+ url = _address + ":" + v_port.value;
+ if (v_username.value || v_password.value) {
+ url = encodeURIComponent(v_username.value) + ":" + encodeURIComponent(v_password.value) + "@" + url;
+ }
+ var params = "";
+ if (protocol === "https") {
+ params += opt.query("sni", dom_prefix + "tls_serverName");
+ params += opt.query("alpn", dom_prefix + "alpn");
+ params += opt.query("insecure", dom_prefix + "tls_allowInsecure");
+ params += opt.query("pcs", dom_prefix + "tls_pinSHA256");
+ params += opt.query("vcn", dom_prefix + "tls_CertByName");
+ if (opt.get(dom_prefix + "utls")?.checked) {
+ params += opt.query("fp", dom_prefix + "fingerprint");
+ }
+ if (opt.get(dom_prefix + "ech")?.checked) {
+ params += opt.query("ech", dom_prefix + "ech_config");
+ }
+ }
+ params += opt.query("tfo", dom_prefix + "tcp_fast_open");
+ if (params) {
+ url += "?" + params.substring(1);
+ }
+ url += "#" + encodeURIComponent(v_alias.value);
}
if (url) {
@@ -1828,6 +1900,126 @@ local current_node = map:get(section)
}
}
+ if (link_type === "wireguard") {
+ if (other_type == "sing-box" && has_singbox) {
+ dom_prefix = "singbox_";
+ opt.set('type', "sing-box");
+ } else if (other_type == "xray" && has_xray) {
+ dom_prefix = "xray_";
+ opt.set('type', "Xray");
+ }
+ opt.set(dom_prefix + 'protocol', "wireguard");
+ var info = link_split[1].split('@');
+ var m = parseNodeUrl("wireguard://" + info[1]);
+ var host_port = info[1].split(/[/?#]/)[0];
+ opt.set(dom_prefix + 'address', unbracketIP(m.hostname));
+ opt.set(dom_prefix + 'port', host_port.match(/:(\d+)$/)?.[1] || '51820');
+ opt.set(dom_prefix + 'wireguard_secret_key', decodeURIComponent(info[0]));
+ var queryParam = {};
+ if (m.search.length > 1) {
+ var queryArray = m.search.substr(1).split('&');
+ for (var i = 0; i < queryArray.length; i++) {
+ var index = queryArray[i].indexOf('=');
+ if (index > 0) {
+ queryParam[decodeURIComponent(queryArray[i].substr(0, index)).toLowerCase()] = decodeURIComponent(queryArray[i].substr(index + 1));
+ }
+ }
+ }
+ opt.set(dom_prefix + 'wireguard_public_key', queryParam.publickey);
+ opt.set(dom_prefix + 'wireguard_preSharedKey', queryParam.presharedkey || '');
+ var addresses = ((queryParam.address || queryParam.ip || '') + ',' + (queryParam.ipv6 || '')).split(/[,\s]+/).filter(Boolean);
+ var fullName = opt.base + '.' + dom_prefix + 'wireguard_local_address';
+ var lists = document.querySelectorAll('.cbi-dynlist');
+ for (var i = 0; i < lists.length; i++) {
+ var label = lists[i].closest('.cbi-value')?.querySelector('label.cbi-value-title');
+ if (label?.getAttribute('for') === fullName) {
+ L.dom.callClassMethod(lists[i], 'setValue', addresses);
+ lists[i].dispatchEvent(new Event('change', { bubbles: true }));
+ break;
+ }
+ }
+ opt.set(dom_prefix + 'wireguard_reserved', queryParam.reserved || '');
+ opt.set(dom_prefix + 'wireguard_mtu', queryParam.mtu || '1420');
+ opt.set(dom_prefix + 'wireguard_keepAlive', queryParam['persistent-keepalive'] || queryParam.keepalive || '0');
+ if (dom_prefix === "xray_") {
+ opt.set(dom_prefix + 'use_finalmask', !!queryParam.fm);
+ opt.set(dom_prefix + 'finalmask', queryParam.fm || '');
+ }
+ if (m.hash) {
+ opt.set('remarks', decodeURIComponent(m.hash.substr(1)));
+ }
+ }
+
+ if (link_type === "socks" || link_type === "socks5" || link_type === "socks5h" || link_type === "http" || link_type === "https") {
+ if (other_type == "sing-box" && has_singbox) {
+ dom_prefix = "singbox_";
+ opt.set('type', "sing-box");
+ } else if (other_type == "xray" && has_xray) {
+ dom_prefix = "xray_";
+ opt.set('type', "Xray");
+ }
+ var protocol = (link_type === "http" || link_type === "https") ? "http" : "socks";
+ opt.set(dom_prefix + 'protocol', protocol);
+ var content = link_split[1];
+ var index = content.search(/[?#]/);
+ var suffix = index >= 0 ? content.substr(index) : '';
+ content = index >= 0 ? content.substr(0, index) : content;
+ var base64_auth = false;
+ if (protocol === "socks" && content.indexOf('@') === -1 && content.indexOf(':') === -1) {
+ content = b64decutf8safe(decodeURIComponent(content));
+ base64_auth = true;
+ }
+ var auth = '';
+ var index = content.lastIndexOf('@');
+ if (index >= 0) {
+ auth = content.substr(0, index);
+ content = content.substr(index + 1);
+ }
+ if (protocol === "socks" && auth.indexOf(':') === -1) {
+ auth = b64decutf8safe(decodeURIComponent(auth));
+ base64_auth = true;
+ }
+ var index = auth.indexOf(':');
+ var username = index >= 0 ? auth.substr(0, index) : auth;
+ var password = index >= 0 ? auth.substr(index + 1) : '';
+ if (!base64_auth) {
+ username = decodeURIComponent(username);
+ password = decodeURIComponent(password);
+ }
+ var m = new URL(link_type + "://" + content + suffix);
+ opt.set(dom_prefix + 'address', unbracketIP(m.hostname));
+ opt.set(dom_prefix + 'port', m.port || (protocol === "socks" ? "1080" : (link_type === "https" ? "443" : "80")));
+ opt.set(dom_prefix + 'username', username);
+ opt.set(dom_prefix + 'password', password);
+ opt.set(dom_prefix + 'transport', dom_prefix === "xray_" ? "raw" : "tcp");
+ var queryParam = {};
+ if (m.search.length > 1) {
+ var queryArray = m.search.substr(1).split('&');
+ for (var i = 0; i < queryArray.length; i++) {
+ var index = queryArray[i].indexOf('=');
+ if (index > 0) {
+ queryParam[decodeURIComponent(queryArray[i].substr(0, index)).toLowerCase()] = decodeURIComponent(queryArray[i].substr(index + 1));
+ }
+ }
+ }
+ opt.set(dom_prefix + 'uot', queryParam.uot === '1');
+ opt.set(dom_prefix + 'tcp_fast_open', queryParam.tfo === '1');
+ opt.set(dom_prefix + 'tls', link_type === "https");
+ opt.set(dom_prefix + 'reality', false);
+ opt.set(dom_prefix + 'tls_serverName', queryParam.sni || '');
+ opt.set(dom_prefix + 'alpn', queryParam.alpn || 'default');
+ opt.set(dom_prefix + 'tls_allowInsecure', (queryParam.allowinsecure || queryParam.insecure || queryParam.allow_insecure || '0') === '1');
+ opt.set(dom_prefix + 'tls_pinSHA256', queryParam.pcs || '');
+ opt.set(dom_prefix + 'tls_CertByName', queryParam.vcn || '');
+ opt.set(dom_prefix + 'utls', !!queryParam.fp);
+ opt.set(dom_prefix + 'fingerprint', queryParam.fp || 'chrome');
+ opt.set(dom_prefix + 'ech', !!queryParam.ech);
+ opt.set(dom_prefix + 'ech_config', queryParam.ech || '');
+ if (m.hash) {
+ opt.set('remarks', decodeURIComponent(m.hash.substr(1)));
+ }
+ }
+
if (switch_type) {
sessionStorage.setItem("fromUrl", JSON.stringify({
timestamp: Date.now(),
diff --git a/luci-app-passwall/po/zh-cn/passwall.po b/luci-app-passwall/po/zh-cn/passwall.po
index 1cf806af..56045219 100644
--- a/luci-app-passwall/po/zh-cn/passwall.po
+++ b/luci-app-passwall/po/zh-cn/passwall.po
@@ -1313,12 +1313,18 @@ msgstr "是否允许不安全连接。当勾选时,将跳过证书验证。"
msgid "%s Node Use Type"
msgstr "%s 节点使用类型"
+msgid "Other"
+msgstr "其他"
+
msgid "UDP relay mode"
msgstr "UDP 中继模式"
msgid "Congestion control algorithm"
msgstr "拥塞控制算法"
+msgid "Keep Alive"
+msgstr "保活间隔"
+
msgid "Heartbeat interval(second)"
msgstr "保活心跳包发送间隔(单位:秒)"
diff --git a/luci-app-passwall/root/usr/share/passwall/subscribe.lua b/luci-app-passwall/root/usr/share/passwall/subscribe.lua
index 356d6f23..1464e4da 100755
--- a/luci-app-passwall/root/usr/share/passwall/subscribe.lua
+++ b/luci-app-passwall/root/usr/share/passwall/subscribe.lua
@@ -86,6 +86,7 @@ local DEFAULT_TROJAN_TYPE = api.get_core("trojan_type", {{has_singbox,"sing-box
local DEFAULT_VMESS_TYPE = api.get_core("vmess_type", {{has_xray,"xray"},{has_singbox,"sing-box"}})
local DEFAULT_VLESS_TYPE = api.get_core("vless_type", {{has_xray,"xray"},{has_singbox,"sing-box"}})
local DEFAULT_HYSTERIA2_TYPE = api.get_core("hysteria2_type", {{has_hysteria2,"hysteria2"},{has_singbox,"sing-box"},{has_xray,"xray"}})
+local DEFAULT_OTHER_TYPE = api.get_core("other_type", {{has_singbox,"sing-box"},{has_xray,"xray"}})
local core_has = {
["xray"] = has_xray,
["sing-box"] = has_singbox,
@@ -569,6 +570,7 @@ local function processData(szType, content, add_mode, group, sub_cfg)
local sub_vmess_type = DEFAULT_VMESS_TYPE
local sub_vless_type = DEFAULT_VLESS_TYPE
local sub_hysteria2_type = DEFAULT_HYSTERIA2_TYPE
+ local sub_other_type = DEFAULT_OTHER_TYPE
local sub_hy_up_mbps, sub_hy_down_mbps = 1000, 1000
if sub_cfg then
if sub_cfg.allowInsecure and sub_cfg.allowInsecure ~= "1" then
@@ -594,6 +596,10 @@ local function processData(szType, content, add_mode, group, sub_cfg)
if hysteria2_type ~= "global" and core_has[hysteria2_type] then
sub_hysteria2_type = hysteria2_type
end
+ local other_type = sub_cfg.other_type or "global"
+ if other_type ~= "global" and core_has[other_type] then
+ sub_other_type = other_type
+ end
sub_hy_up_mbps = sub_cfg.hysteria_up_mbps
sub_hy_down_mbps = sub_cfg.hysteria_down_mbps
end
@@ -1664,6 +1670,115 @@ local function processData(szType, content, add_mode, group, sub_cfg)
result.snell_mode = params.mode or "default"
end
result.snell_reuse = (params.reuse == "1") and "1" or "0"
+ elseif szType == "wireguard" then
+ if sub_other_type == "sing-box" and has_singbox then
+ result.type = "sing-box"
+ elseif sub_other_type == "xray" and has_xray then
+ result.type = "Xray"
+ else
+ log("跳过 WireGuard 节点,因未安装 WireGuard 核心程序 Xray 或 Sing-box。")
+ return nil
+ end
+ result.protocol = "wireguard"
+
+ local alias = ""
+ if content:find("#") then
+ local idx_sp = content:find("#")
+ alias = content:sub(idx_sp + 1, -1)
+ content = content:sub(0, idx_sp - 1)
+ end
+ result.remarks = UrlDecode(alias)
+ local contents = split(content, "@")
+ result.wireguard_secret_key = UrlDecode(contents[1]:gsub("%+", "%%2B"))
+ local query = split(contents[2], "%?")
+ local host_port = query[1]
+ local params = {}
+ for _, v in pairs(split(query[2], '&')) do
+ local s = v:find("=", 1, true)
+ if s and s > 1 then
+ params[UrlDecode(v:sub(1, s - 1)):lower()] = UrlDecode(v:sub(s + 1):gsub("%+", "%%2B"))
+ end
+ end
+ result.address, result.port = parse_host_port(host_port, "51820")
+ result.wireguard_public_key = params.publickey
+ result.wireguard_preSharedKey = params.presharedkey
+ result.wireguard_local_address = {}
+ for value in ((params.address or params.ip or "") .. "," .. (params.ipv6 or "")):gmatch("[^,%s]+") do
+ result.wireguard_local_address[#result.wireguard_local_address + 1] = value
+ end
+ result.wireguard_reserved = params.reserved
+ result.wireguard_mtu = params.mtu or "1420"
+ result.wireguard_keepAlive = params["persistent-keepalive"] or params.keepalive or "0"
+ result.use_finalmask = (params.fm and params.fm ~= "") and "1" or nil
+ result.finalmask = (params.fm and params.fm ~= "") and api.base64Encode(params.fm) or nil
+ elseif szType == "socks" or szType == "socks5" or szType == "socks5h" or szType == "http" or szType == "https" then
+ if sub_other_type == "sing-box" and has_singbox then
+ result.type = "sing-box"
+ elseif sub_other_type == "xray" and has_xray then
+ result.type = "Xray"
+ else
+ log("跳过 " .. szType .. " 节点,因未安装 " .. szType .. " 核心程序 Xray 或 Sing-box。")
+ return nil
+ end
+ result.protocol = (szType == "http" or szType == "https") and "http" or "socks"
+
+ local alias = ""
+ if content:find("#") then
+ local idx_sp = content:find("#")
+ alias = content:sub(idx_sp + 1, -1)
+ content = content:sub(0, idx_sp - 1)
+ end
+ result.remarks = UrlDecode(alias)
+ local query = split(content, "%?")
+ local Info = query[1]
+ local base64_auth = false
+ if result.protocol == "socks" and not Info:find("@", 1, true) and not Info:find(":", 1, true) then
+ Info = base64Decode(UrlDecode(Info:gsub("%+", "%%2B")))
+ base64_auth = true
+ end
+ local auth, host_port = Info:match("^(.*)@(.*)$")
+ host_port = host_port or Info
+ auth = auth or ""
+ if result.protocol == "socks" and not auth:find(":", 1, true) then
+ auth = base64Decode(UrlDecode(auth:gsub("%+", "%%2B")))
+ base64_auth = true
+ end
+ local idx = auth:find(":", 1, true)
+ result.username = idx and auth:sub(1, idx - 1) or auth
+ result.password = idx and auth:sub(idx + 1) or ""
+ if not base64_auth then
+ result.username = UrlDecode(result.username:gsub("%+", "%%2B"))
+ result.password = UrlDecode(result.password:gsub("%+", "%%2B"))
+ end
+ local params = {}
+ for _, v in pairs(split(query[2], '&')) do
+ local s = v:find("=", 1, true)
+ if s and s > 1 then
+ params[UrlDecode(v:sub(1, s - 1)):lower()] = UrlDecode(v:sub(s + 1):gsub("%+", "%%2B"))
+ end
+ end
+ local port = result.protocol == "socks" and "1080" or (szType == "https" and "443" or "80")
+ result.address, result.port = parse_host_port(host_port, port)
+ result.transport = result.type == "Xray" and "raw" or "tcp"
+ result.uot = params.uot
+ result.tcp_fast_open = params.tfo
+ result.tls = szType == "https" and "1" or "0"
+ if result.tls == "1" then
+ result.tls_serverName = params.sni
+ result.alpn = params.alpn
+ local insecure = params.allowinsecure or params.insecure or params.allow_insecure
+ result.tls_allowInsecure = (insecure == "1" or insecure == "0") and insecure or (sub_allowinsecure and "1" or "0")
+ result.tls_pinSHA256 = params.pcs
+ result.tls_CertByName = params.vcn
+ if params.fp and params.fp ~= "" then
+ result.utls = "1"
+ result.fingerprint = params.fp
+ end
+ if params.ech and params.ech ~= "" then
+ result.ech = "1"
+ result.ech_config = params.ech
+ end
+ end
else
log("暂时不支持 " .. szType .. " 类型的节点订阅,跳过此节点。")
return nil
diff --git a/luci-app-passwall2/Makefile b/luci-app-passwall2/Makefile
index 57b72e6c..4373278c 100644
--- a/luci-app-passwall2/Makefile
+++ b/luci-app-passwall2/Makefile
@@ -6,7 +6,7 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-passwall2
-PKG_VERSION:=26.11.3
+PKG_VERSION:=26.11.11
PKG_RELEASE:=1
PKG_PO_VERSION:=$(PKG_VERSION)
diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe.lua
index 4b09a522..662b06e8 100644
--- a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe.lua
+++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe.lua
@@ -9,6 +9,7 @@ local trojan_type = {}
local vmess_type = {}
local vless_type = {}
local hysteria2_type = {}
+local other_type = {}
local xray_version = api.get_app_version("xray")
if has_ss_rust then
local s = "shadowsocks-rust"
@@ -21,6 +22,7 @@ if has_singbox then
table.insert(vmess_type, s)
table.insert(vless_type, s)
table.insert(hysteria2_type, s)
+ table.insert(other_type, s)
end
if has_xray then
local s = "xray"
@@ -28,6 +30,7 @@ if has_xray then
table.insert(ss_type, s)
table.insert(vmess_type, s)
table.insert(vless_type, s)
+ table.insert(other_type, s)
if api.compare_versions(xray_version, ">=", "26.1.13") then
table.insert(hysteria2_type, s)
end
@@ -95,7 +98,15 @@ if #hysteria2_type > 0 then
end
end
-if #ss_type > 0 or #trojan_type > 0 or #vmess_type > 0 or #vless_type > 0 or #hysteria2_type > 0 then
+if #other_type > 0 then
+ o = s:option(ListValue, "other_type", translatef("%s Node Use Type", translate("Other")))
+ o:value("", translate("Auto"))
+ for key, value in pairs(other_type) do
+ o:value(value)
+ end
+end
+
+if #ss_type > 0 or #trojan_type > 0 or #vmess_type > 0 or #vless_type > 0 or #hysteria2_type > 0 or #other_type > 0 then
o.description = string.format("%s",
translate("The configured type also applies to the core specified when manually importing nodes."))
end
diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe_config.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe_config.lua
index 36407bdc..2d5f594b 100644
--- a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe_config.lua
+++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe_config.lua
@@ -22,6 +22,7 @@ local trojan_type = {}
local vmess_type = {}
local vless_type = {}
local hysteria2_type = {}
+local other_type = {}
local xray_version = api.get_app_version("xray")
if has_ss_rust then
local s = "shadowsocks-rust"
@@ -34,6 +35,7 @@ if has_singbox then
table.insert(vmess_type, s)
table.insert(vless_type, s)
table.insert(hysteria2_type, s)
+ table.insert(other_type, s)
end
if has_xray then
local s = "xray"
@@ -41,6 +43,7 @@ if has_xray then
table.insert(ss_type, s)
table.insert(vmess_type, s)
table.insert(vless_type, s)
+ table.insert(other_type, s)
if api.compare_versions(xray_version, ">=", "26.1.13") then
table.insert(hysteria2_type, s)
end
@@ -215,7 +218,18 @@ if #hysteria2_type > 0 then
for key, value in pairs(hysteria2_type) do
o:value(value)
end
+end
+if #other_type > 0 then
+ o = s:option(ListValue, "other_type", translatef("%s Node Use Type", translate("Other")))
+ o.default = "global"
+ o:value("global", translate("Use global config"))
+ for key, value in pairs(other_type) do
+ o:value(value)
+ end
+end
+
+if #hysteria2_type > 0 then
o = s:option(Value, "hysteria_up_mbps", "Hy/Hy2 " .. translate("Max upload Mbps"))
o.datatype = "uinteger"
diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/1_sing-box.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/1_sing-box.lua
index 5a9587fb..fe73e27f 100644
--- a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/1_sing-box.lua
+++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/1_sing-box.lua
@@ -768,6 +768,10 @@ if singbox_tags:find("with_wireguard") then
o = s:option(Value, "wireguard_reserved", translate("Reserved"), translate("Decimal numbers separated by \",\" or Base64-encoded strings."))
o:depends({ protocol = "wireguard" })
+
+ o = s:option(Value, "wireguard_keepAlive", translate("Keep Alive"))
+ o.default = "0"
+ o:depends({ protocol = "wireguard" })
end
-- [[ TCP ]]--
@@ -810,16 +814,6 @@ o = s:option(Value, "ws_path", translate("WebSocket Path"))
o.placeholder = "/"
o:depends({ transport = "ws" })
-o = s:option(Flag, "ws_enableEarlyData", translate("Enable early data"))
-o:depends({ transport = "ws" })
-
-o = s:option(Value, "ws_maxEarlyData", translate("Early data length"))
-o.default = "1024"
-o:depends({ ws_enableEarlyData = true })
-
-o = s:option(Value, "ws_earlyDataHeaderName", translate("Early data header name"), translate("Recommended value: Sec-WebSocket-Protocol"))
-o:depends({ ws_enableEarlyData = true })
-
-- [[ HTTPUpgrade ]]--
o = s:option(Value, "httpupgrade_host", translate("HTTPUpgrade Host"))
o:depends({ transport = "httpupgrade" })
diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/1_sing-box.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/1_sing-box.lua
index 2f6c9481..8815f57d 100644
--- a/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/1_sing-box.lua
+++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/1_sing-box.lua
@@ -459,6 +459,10 @@ o:depends({ transport = "ws" })
o = s:option(Value, "ws_path", translate("WebSocket Path"))
o:depends({ transport = "ws" })
+o = s:option(Value, "ws_earlyDataHeaderName", translate("Early data header name"), translate("Recommended value: Sec-WebSocket-Protocol"))
+o.placeholder = "Sec-WebSocket-Protocol"
+o:depends({ transport = "ws" })
+
-- [[ HTTPUpgrade ]]--
o = s:option(Value, "httpupgrade_host", translate("HTTPUpgrade Host"))
diff --git a/luci-app-passwall2/luasrc/passwall2/util_sing-box.lua b/luci-app-passwall2/luasrc/passwall2/util_sing-box.lua
index a73d8949..c5e030e0 100644
--- a/luci-app-passwall2/luasrc/passwall2/util_sing-box.lua
+++ b/luci-app-passwall2/luasrc/passwall2/util_sing-box.lua
@@ -366,9 +366,26 @@ function gen_outbound(flag, node, tag, proxy_table)
Host = node.ws_host,
["User-Agent"] = node.user_agent
} or nil,
- max_early_data = tonumber(node.ws_maxEarlyData) or nil,
- early_data_header_name = (node.ws_earlyDataHeaderName) and node.ws_earlyDataHeaderName or nil -- For compatibility with Xray-core, set it to Sec-WebSocket-Protocol. It needs to be consistent with the server.
}
+ local path, query = (node.ws_path or "/"):match("^(.-)%?(.*)$")
+ local params, query_params = {}, {}
+ for v in (query or ""):gmatch("[^&]+") do
+ local t = api.split(api.UrlDecode(v), '=')
+ params[t[1]] = t[2]
+ if t[1] ~= "ed" and t[1] ~= "eh" then
+ table.insert(query_params, v)
+ end
+ end
+ local ed = tonumber(params.ed)
+ if ed then
+ -- Consume early-data options while preserving the remaining query.
+ v2ray_transport.path = path
+ if #query_params > 0 then
+ v2ray_transport.path = path .. "?" .. table.concat(query_params, "&")
+ end
+ v2ray_transport.max_early_data = ed
+ end
+ v2ray_transport.early_data_header_name = params.eh or "Sec-WebSocket-Protocol"
end
if node.transport == "httpupgrade" then
@@ -506,6 +523,7 @@ function gen_outbound(flag, node, tag, proxy_table)
pre_shared_key = node.wireguard_preSharedKey,
reserved = node.wireguard_reserved,
mtu = tonumber(node.wireguard_mtu),
+ persistent_keepalive_interval = tonumber(node.wireguard_keepAlive),
}
end
@@ -822,7 +840,7 @@ function gen_config_server(node)
type = "ws",
path = node.ws_path or "/",
headers = (node.ws_host ~= nil) and { Host = node.ws_host } or nil,
- early_data_header_name = (node.ws_earlyDataHeaderName) and node.ws_earlyDataHeaderName or nil -- For compatibility with Xray-core, set it to Sec-WebSocket-Protocol. It needs to be consistent with the server.
+ early_data_header_name = (node.ws_earlyDataHeaderName) and node.ws_earlyDataHeaderName or "Sec-WebSocket-Protocol"
}
end
@@ -2382,7 +2400,8 @@ function gen_config(var)
port = value.server_port,
public_key = value.peer_public_key,
pre_shared_key = value.pre_shared_key,
- allowed_ips = {"0.0.0.0/0"},
+ allowed_ips = {"0.0.0.0/0", "::/0"},
+ persistent_keepalive_interval = value.persistent_keepalive_interval,
reserved = value.reserved
}
},
diff --git a/luci-app-passwall2/luasrc/passwall2/util_xray.lua b/luci-app-passwall2/luasrc/passwall2/util_xray.lua
index 1a03a216..01c7f568 100644
--- a/luci-app-passwall2/luasrc/passwall2/util_xray.lua
+++ b/luci-app-passwall2/luasrc/passwall2/util_xray.lua
@@ -12,7 +12,7 @@ local GLOBAL = {
local xray_version = api.get_app_version("xray")
-local xray_min_version = "26.7.11"
+local xray_min_version = "26.9.9"
local function get_domain_excluded()
local path = "/usr/share/passwall2/domains_excluded"
@@ -364,6 +364,30 @@ function gen_outbound(flag, node, tag, proxy_table)
}
}
udp[#udp+1] = r
+ elseif node.hysteria2_hop and node.hysteria2_hop ~= "" then
+ udp[#udp+1] = {
+ type = "udphop",
+ settings = {
+ mode = "intervallocal,intervalremote",
+ remotePorts = string.gsub(node.hysteria2_hop, ":", "-"),
+ interval = (function(v)
+ if not v then return 30 end
+ if v:find("-", 1, true) then
+ local min, max = v:match("^(%d+)%-(%d+)$")
+ min = tonumber(min)
+ max = tonumber(max)
+ if min and max then
+ min = (min >= 5) and min or 5
+ max = (max >= min) and max or min
+ return min .. "-" .. max
+ end
+ return 30
+ end
+ v = tonumber((v or "30"):match("^%d+"))
+ return (v and v >= 5) and v or 30
+ end)(node.hysteria2_hop_interval)
+ }
+ }
end
finalmask.udp = udp
local up = tonumber(node.hysteria2_up_mbps) or 0
@@ -372,25 +396,6 @@ function gen_outbound(flag, node, tag, proxy_table)
congestion = (up <= 0 and down <= 0) and "bbr" or "brutal",
brutalUp = up > 0 and (up .. "mbps") or nil,
brutalDown = down > 0 and (down .. "mbps") or nil,
- udpHop = (node.hysteria2_hop) and {
- ports = string.gsub(node.hysteria2_hop, ":", "-"),
- interval = (function(v)
- if not v then return 30 end
- if v:find("-", 1, true) then
- local min, max = v:match("^(%d+)%-(%d+)$")
- min = tonumber(min)
- max = tonumber(max)
- if min and max then
- min = (min >= 5) and min or 5
- max = (max >= min) and max or min
- return min .. "-" .. max
- end
- return 30
- end
- v = tonumber((v or "30"):match("^%d+"))
- return (v and v >= 5) and v or 30
- end)(node.hysteria2_hop_interval)
- } or nil,
maxIdleTimeout = (function(t)
t = tonumber(tostring(t or "30"):match("^%d+"))
return (t and t >= 4 and t <= 120) and t or 30
@@ -440,7 +445,7 @@ function gen_outbound(flag, node, tag, proxy_table)
peers = (node.protocol == "wireguard") and {
{
publicKey = node.wireguard_public_key,
- endpoint = node.address .. ":" .. node.port,
+ endpoint = (api.is_ipv6(node.address) and ("[" .. node.address .. "]") or node.address) .. ":" .. node.port,
preSharedKey = node.wireguard_preSharedKey,
keepAlive = node.wireguard_keepAlive and tonumber(node.wireguard_keepAlive) or nil
}
diff --git a/luci-app-passwall2/luasrc/view/passwall2/node_config/link_share_man.htm b/luci-app-passwall2/luasrc/view/passwall2/node_config/link_share_man.htm
index 7741cca5..ba9555dd 100644
--- a/luci-app-passwall2/luasrc/view/passwall2/node_config/link_share_man.htm
+++ b/luci-app-passwall2/luasrc/view/passwall2/node_config/link_share_man.htm
@@ -13,6 +13,7 @@ local trojan_type = api.get_core("trojan_type", {{has_singbox,"sing-box"},{has_x
local vmess_type = api.get_core("vmess_type", {{has_xray,"xray"},{has_singbox,"sing-box"}})
local vless_type = api.get_core("vless_type", {{has_xray,"xray"},{has_singbox,"sing-box"}})
local hysteria2_type = api.get_core("hysteria2_type", {{has_singbox,"sing-box"}, {has_xray,"xray"}})
+local other_type = api.get_core("other_type", {{has_singbox,"sing-box"},{has_xray,"xray"}})
local current_node = map:get(section)
-%>
@@ -25,6 +26,7 @@ local current_node = map:get(section)
let vmess_type = "<%=vmess_type%>"
let vless_type = "<%=vless_type%>"
let hysteria2_type = "<%=hysteria2_type%>"
+ let other_type = "<%=other_type%>"
function padright(str, cnt, pad) {
return str + Array(cnt + 1).join(pad);
@@ -254,10 +256,6 @@ local current_node = map:get(section)
if (v_transport === "ws") {
params += opt.query("host", dom_prefix + "ws_host");
params += opt.query("path", dom_prefix + "ws_path");
- if (v_type == "sing-box" && opt.get(dom_prefix + "ws_enableEarlyData").checked) {
- var ws_maxEarlyData = opt.get(dom_prefix + "ws_maxEarlyData").value;
- params += "?ed=" + ws_maxEarlyData;
- }
} else if (v_transport === "http") {
params += opt.query("host", dom_prefix + "http_host");
params += opt.query("path", dom_prefix + "http_path");
@@ -376,10 +374,6 @@ local current_node = map:get(section)
if (v_transport === "ws") {
info.host = opt.get(dom_prefix + "ws_host").value;
info.path = opt.get(dom_prefix + "ws_path").value;
- if (v_type == "sing-box" && opt.get(dom_prefix + "ws_enableEarlyData").checked) {
- var ws_maxEarlyData = opt.get(dom_prefix + "ws_maxEarlyData").value;
- info.path = info.path + "?ed=" + ws_maxEarlyData;
- }
} else if (v_transport === "http") {
info.host = opt.get(dom_prefix + "http_host").value;
info.path = opt.get(dom_prefix + "http_path").value;
@@ -436,10 +430,6 @@ local current_node = map:get(section)
if (v_transport === "ws") {
params += opt.query("host", dom_prefix + "ws_host");
params += opt.query("path", dom_prefix + "ws_path");
- if (v_type == "sing-box" && opt.get(dom_prefix + "ws_enableEarlyData").checked) {
- var ws_maxEarlyData = opt.get(dom_prefix + "ws_maxEarlyData").value;
- params += encodeURIComponent("?ed=" + ws_maxEarlyData);
- }
} else if (v_transport === "http") {
params += opt.query("host", dom_prefix + "http_host");
params += opt.query("path", dom_prefix + "http_path");
@@ -528,10 +518,6 @@ local current_node = map:get(section)
if (v_transport === "ws") {
params += opt.query("host", dom_prefix + "ws_host");
params += opt.query("path", dom_prefix + "ws_path");
- if (v_type == "sing-box" && opt.get(dom_prefix + "ws_enableEarlyData").checked) {
- var ws_maxEarlyData = opt.get(dom_prefix + "ws_maxEarlyData").value;
- params += "?ed=" + ws_maxEarlyData;
- }
} else if (v_transport === "http") {
params += opt.query("host", dom_prefix + "http_host");
params += opt.query("path", dom_prefix + "http_path");
@@ -729,6 +715,74 @@ local current_node = map:get(section)
params = params.substring(1);
}
url += params;
+ } else if ((v_type === "Xray" || v_type === "sing-box") && opt.get(dom_prefix + "protocol").value === "wireguard") {
+ protocol = "wireguard";
+ var v_password = opt.get(dom_prefix + "wireguard_secret_key");
+ var v_port = opt.get(dom_prefix + "port");
+ url = encodeURIComponent(v_password.value) +
+ "@" + _address +
+ ":" + v_port.value + "?";
+
+ var params = "";
+ params += opt.query("publickey", dom_prefix + "wireguard_public_key");
+ params += opt.query("presharedkey", dom_prefix + "wireguard_preSharedKey");
+ var addresses = opt.getlist(dom_prefix + "wireguard_local_address").map(value => value.trim()).filter(Boolean);
+ params += "&address=" + encodeURIComponent(addresses.join(","));
+ params += opt.query("reserved", dom_prefix + "wireguard_reserved");
+ params += opt.query("mtu", dom_prefix + "wireguard_mtu");
+ params += opt.query("persistent-keepalive", dom_prefix + "wireguard_keepAlive");
+ if (v_type === "Xray" && opt.get(dom_prefix + "use_finalmask")?.checked) {
+ params += opt.query("fm", dom_prefix + "finalmask");
+ }
+ params += "#" + encodeURIComponent(v_alias.value);
+ if (params[0] == "&") {
+ params = params.substring(1);
+ }
+ url += params;
+ } else if ((v_type === "sing-box" || v_type === "Xray") && opt.get(dom_prefix + "protocol").value === "socks") {
+ protocol = "socks";
+ var v_username = opt.get(dom_prefix + "username");
+ var v_password = opt.get(dom_prefix + "password");
+ var v_port = opt.get(dom_prefix + "port");
+ url = _address + ":" + v_port.value;
+ if (v_username.value || v_password.value) {
+ url = b64encutf8safe(v_username.value + ":" + v_password.value) + "@" + url;
+ }
+ var params = "";
+ params += opt.query("uot", dom_prefix + "uot");
+ params += opt.query("tfo", dom_prefix + "tcp_fast_open");
+ if (params) {
+ url += "?" + params.substring(1);
+ }
+ url += "#" + encodeURIComponent(v_alias.value);
+ } else if ((v_type === "sing-box" || v_type === "Xray") && opt.get(dom_prefix + "protocol").value === "http") {
+ protocol = opt.get(dom_prefix + "tls").checked ? "https" : "http";
+ var v_username = opt.get(dom_prefix + "username");
+ var v_password = opt.get(dom_prefix + "password");
+ var v_port = opt.get(dom_prefix + "port");
+ url = _address + ":" + v_port.value;
+ if (v_username.value || v_password.value) {
+ url = encodeURIComponent(v_username.value) + ":" + encodeURIComponent(v_password.value) + "@" + url;
+ }
+ var params = "";
+ if (protocol === "https") {
+ params += opt.query("sni", dom_prefix + "tls_serverName");
+ params += opt.query("alpn", dom_prefix + "alpn");
+ params += opt.query("insecure", dom_prefix + "tls_allowInsecure");
+ params += opt.query("pcs", dom_prefix + "tls_pinSHA256");
+ params += opt.query("vcn", dom_prefix + "tls_CertByName");
+ if (opt.get(dom_prefix + "utls")?.checked) {
+ params += opt.query("fp", dom_prefix + "fingerprint");
+ }
+ if (opt.get(dom_prefix + "ech")?.checked) {
+ params += opt.query("ech", dom_prefix + "ech_config");
+ }
+ }
+ params += opt.query("tfo", dom_prefix + "tcp_fast_open");
+ if (params) {
+ url += "?" + params.substring(1);
+ }
+ url += "#" + encodeURIComponent(v_alias.value);
}
if (url) {
@@ -1111,24 +1165,6 @@ local current_node = map:get(section)
} else if (queryParam.type === "ws") {
opt.set(dom_prefix + 'ws_host', queryParam.host || "");
opt.set(dom_prefix + 'ws_path', queryParam.path || "");
- if (dom_prefix == "singbox_" && queryParam.path && queryParam.path.length > 1) {
- var ws_path_params = {};
- var ws_path_dat = queryParam.path.split('?');
- var ws_path = ws_path_dat[0];
- var ws_path_params = {};
- var ws_path_params_array = (ws_path_dat[1] || '').split('&');
- for (i = 0; i < ws_path_params_array.length; i++) {
- var kv = ws_path_params_array[i].split('=');
- ws_path_params[decodeURIComponent(kv[0]).toLowerCase()] = decodeURIComponent(kv[1] || '');
- }
-
- if (ws_path_params.ed) {
- opt.set(dom_prefix + 'ws_path', ws_path);
- opt.set(dom_prefix + 'ws_enableEarlyData', true);
- opt.set(dom_prefix + 'ws_maxEarlyData', ws_path_params.ed);
- opt.set(dom_prefix + 'ws_earlyDataHeaderName', 'Sec-WebSocket-Protocol');
- }
- }
} else if (queryParam.type === "http") {
if (dom_prefix == "xray_") {
opt.set(dom_prefix + 'xhttp_mode', "stream-one");
@@ -1259,24 +1295,6 @@ local current_node = map:get(section)
} else if (queryParam.type === "ws") {
opt.set(dom_prefix + 'ws_host', queryParam.host || "");
opt.set(dom_prefix + 'ws_path', queryParam.path || "");
- if (dom_prefix == "singbox_" && queryParam.path && queryParam.path.length > 1) {
- var ws_path_params = {};
- var ws_path_dat = queryParam.path.split('?');
- var ws_path = ws_path_dat[0];
- var ws_path_params = {};
- var ws_path_params_array = (ws_path_dat[1] || '').split('&');
- for (i = 0; i < ws_path_params_array.length; i++) {
- var kv = ws_path_params_array[i].split('=');
- ws_path_params[decodeURIComponent(kv[0]).toLowerCase()] = decodeURIComponent(kv[1] || '');
- }
-
- if (ws_path_params.ed) {
- opt.set(dom_prefix + 'ws_path', ws_path);
- opt.set(dom_prefix + 'ws_enableEarlyData', true);
- opt.set(dom_prefix + 'ws_maxEarlyData', ws_path_params.ed);
- opt.set(dom_prefix + 'ws_earlyDataHeaderName', 'Sec-WebSocket-Protocol');
- }
- }
} else if (queryParam.type === "h2" || queryParam.type === "http") {
if (dom_prefix == "xray_") {
opt.set(dom_prefix + 'xhttp_mode', "stream-one");
@@ -1446,24 +1464,6 @@ local current_node = map:get(section)
} else if (ssm.net === "ws") {
opt.set(dom_prefix + 'ws_host', ssm.host);
opt.set(dom_prefix + 'ws_path', ssm.path);
- if (dom_prefix == "singbox_" && ssm.path && ssm.path.length > 1) {
- var ws_path_params = {};
- var ws_path_dat = ssm.path.split('?');
- var ws_path = ws_path_dat[0];
- var ws_path_params = {};
- var ws_path_params_array = (ws_path_dat[1] || '').split('&');
- for (i = 0; i < ws_path_params_array.length; i++) {
- var kv = ws_path_params_array[i].split('=');
- ws_path_params[decodeURIComponent(kv[0]).toLowerCase()] = decodeURIComponent(kv[1] || '');
- }
-
- if (ws_path_params.ed) {
- opt.set(dom_prefix + 'ws_path', ws_path);
- opt.set(dom_prefix + 'ws_enableEarlyData', true);
- opt.set(dom_prefix + 'ws_maxEarlyData', ws_path_params.ed);
- opt.set(dom_prefix + 'ws_earlyDataHeaderName', 'Sec-WebSocket-Protocol');
- }
- }
} else if (ssm.net === "http") {
if (dom_prefix == "xray_") {
opt.set(dom_prefix + 'xhttp_mode', "stream-one");
@@ -1589,24 +1589,6 @@ local current_node = map:get(section)
} else if (queryParam.type === "ws") {
opt.set(dom_prefix + 'ws_host', queryParam.host || "");
opt.set(dom_prefix + 'ws_path', queryParam.path || "");
- if (dom_prefix == "singbox_" && queryParam.path && queryParam.path.length > 1) {
- var ws_path_params = {};
- var ws_path_dat = queryParam.path.split('?');
- var ws_path = ws_path_dat[0];
- var ws_path_params = {};
- var ws_path_params_array = (ws_path_dat[1] || '').split('&');
- for (i = 0; i < ws_path_params_array.length; i++) {
- var kv = ws_path_params_array[i].split('=');
- ws_path_params[decodeURIComponent(kv[0]).toLowerCase()] = decodeURIComponent(kv[1] || '');
- }
-
- if (ws_path_params.ed) {
- opt.set(dom_prefix + 'ws_path', ws_path);
- opt.set(dom_prefix + 'ws_enableEarlyData', true);
- opt.set(dom_prefix + 'ws_maxEarlyData', ws_path_params.ed);
- opt.set(dom_prefix + 'ws_earlyDataHeaderName', 'Sec-WebSocket-Protocol');
- }
- }
} else if (queryParam.type === "h2" || queryParam.type === "http") {
if (dom_prefix == "xray_") {
opt.set(dom_prefix + 'xhttp_mode', "stream-one");
@@ -1874,6 +1856,126 @@ local current_node = map:get(section)
}
}
+ if (link_type === "wireguard") {
+ if (other_type == "sing-box" && has_singbox) {
+ dom_prefix = "singbox_";
+ opt.set('type', "sing-box");
+ } else if (other_type == "xray" && has_xray) {
+ dom_prefix = "xray_";
+ opt.set('type', "Xray");
+ }
+ opt.set(dom_prefix + 'protocol', "wireguard");
+ var info = link_split[1].split('@');
+ var m = parseNodeUrl("wireguard://" + info[1]);
+ var host_port = info[1].split(/[/?#]/)[0];
+ opt.set(dom_prefix + 'address', unbracketIP(m.hostname));
+ opt.set(dom_prefix + 'port', host_port.match(/:(\d+)$/)?.[1] || '51820');
+ opt.set(dom_prefix + 'wireguard_secret_key', decodeURIComponent(info[0]));
+ var queryParam = {};
+ if (m.search.length > 1) {
+ var queryArray = m.search.substr(1).split('&');
+ for (var i = 0; i < queryArray.length; i++) {
+ var index = queryArray[i].indexOf('=');
+ if (index > 0) {
+ queryParam[decodeURIComponent(queryArray[i].substr(0, index)).toLowerCase()] = decodeURIComponent(queryArray[i].substr(index + 1));
+ }
+ }
+ }
+ opt.set(dom_prefix + 'wireguard_public_key', queryParam.publickey);
+ opt.set(dom_prefix + 'wireguard_preSharedKey', queryParam.presharedkey || '');
+ var addresses = ((queryParam.address || queryParam.ip || '') + ',' + (queryParam.ipv6 || '')).split(/[,\s]+/).filter(Boolean);
+ var fullName = opt.base + '.' + dom_prefix + 'wireguard_local_address';
+ var lists = document.querySelectorAll('.cbi-dynlist');
+ for (var i = 0; i < lists.length; i++) {
+ var label = lists[i].closest('.cbi-value')?.querySelector('label.cbi-value-title');
+ if (label?.getAttribute('for') === fullName) {
+ L.dom.callClassMethod(lists[i], 'setValue', addresses);
+ lists[i].dispatchEvent(new Event('change', { bubbles: true }));
+ break;
+ }
+ }
+ opt.set(dom_prefix + 'wireguard_reserved', queryParam.reserved || '');
+ opt.set(dom_prefix + 'wireguard_mtu', queryParam.mtu || '1420');
+ opt.set(dom_prefix + 'wireguard_keepAlive', queryParam['persistent-keepalive'] || queryParam.keepalive || '0');
+ if (dom_prefix === "xray_") {
+ opt.set(dom_prefix + 'use_finalmask', !!queryParam.fm);
+ opt.set(dom_prefix + 'finalmask', queryParam.fm || '');
+ }
+ if (m.hash) {
+ opt.set('remarks', decodeURIComponent(m.hash.substr(1)));
+ }
+ }
+
+ if (link_type === "socks" || link_type === "socks5" || link_type === "socks5h" || link_type === "http" || link_type === "https") {
+ if (other_type == "sing-box" && has_singbox) {
+ dom_prefix = "singbox_";
+ opt.set('type', "sing-box");
+ } else if (other_type == "xray" && has_xray) {
+ dom_prefix = "xray_";
+ opt.set('type', "Xray");
+ }
+ var protocol = (link_type === "http" || link_type === "https") ? "http" : "socks";
+ opt.set(dom_prefix + 'protocol', protocol);
+ var content = link_split[1];
+ var index = content.search(/[?#]/);
+ var suffix = index >= 0 ? content.substr(index) : '';
+ content = index >= 0 ? content.substr(0, index) : content;
+ var base64_auth = false;
+ if (protocol === "socks" && content.indexOf('@') === -1 && content.indexOf(':') === -1) {
+ content = b64decutf8safe(decodeURIComponent(content));
+ base64_auth = true;
+ }
+ var auth = '';
+ var index = content.lastIndexOf('@');
+ if (index >= 0) {
+ auth = content.substr(0, index);
+ content = content.substr(index + 1);
+ }
+ if (protocol === "socks" && auth.indexOf(':') === -1) {
+ auth = b64decutf8safe(decodeURIComponent(auth));
+ base64_auth = true;
+ }
+ var index = auth.indexOf(':');
+ var username = index >= 0 ? auth.substr(0, index) : auth;
+ var password = index >= 0 ? auth.substr(index + 1) : '';
+ if (!base64_auth) {
+ username = decodeURIComponent(username);
+ password = decodeURIComponent(password);
+ }
+ var m = new URL(link_type + "://" + content + suffix);
+ opt.set(dom_prefix + 'address', unbracketIP(m.hostname));
+ opt.set(dom_prefix + 'port', m.port || (protocol === "socks" ? "1080" : (link_type === "https" ? "443" : "80")));
+ opt.set(dom_prefix + 'username', username);
+ opt.set(dom_prefix + 'password', password);
+ opt.set(dom_prefix + 'transport', dom_prefix === "xray_" ? "raw" : "tcp");
+ var queryParam = {};
+ if (m.search.length > 1) {
+ var queryArray = m.search.substr(1).split('&');
+ for (var i = 0; i < queryArray.length; i++) {
+ var index = queryArray[i].indexOf('=');
+ if (index > 0) {
+ queryParam[decodeURIComponent(queryArray[i].substr(0, index)).toLowerCase()] = decodeURIComponent(queryArray[i].substr(index + 1));
+ }
+ }
+ }
+ opt.set(dom_prefix + 'uot', queryParam.uot === '1');
+ opt.set(dom_prefix + 'tcp_fast_open', queryParam.tfo === '1');
+ opt.set(dom_prefix + 'tls', link_type === "https");
+ opt.set(dom_prefix + 'reality', false);
+ opt.set(dom_prefix + 'tls_serverName', queryParam.sni || '');
+ opt.set(dom_prefix + 'alpn', queryParam.alpn || 'default');
+ opt.set(dom_prefix + 'tls_allowInsecure', (queryParam.allowinsecure || queryParam.insecure || queryParam.allow_insecure || '0') === '1');
+ opt.set(dom_prefix + 'tls_pinSHA256', queryParam.pcs || '');
+ opt.set(dom_prefix + 'tls_CertByName', queryParam.vcn || '');
+ opt.set(dom_prefix + 'utls', !!queryParam.fp);
+ opt.set(dom_prefix + 'fingerprint', queryParam.fp || 'chrome');
+ opt.set(dom_prefix + 'ech', !!queryParam.ech);
+ opt.set(dom_prefix + 'ech_config', queryParam.ech || '');
+ if (m.hash) {
+ opt.set('remarks', decodeURIComponent(m.hash.substr(1)));
+ }
+ }
+
if (switch_type) {
sessionStorage.setItem("fromUrl", JSON.stringify({
timestamp: Date.now(),
diff --git a/luci-app-passwall2/po/fa/passwall2.po b/luci-app-passwall2/po/fa/passwall2.po
index 2f264474..2b9994f1 100644
--- a/luci-app-passwall2/po/fa/passwall2.po
+++ b/luci-app-passwall2/po/fa/passwall2.po
@@ -604,6 +604,9 @@ msgstr "پنجره دریافت اتصال QUIC"
msgid "QUIC concurrent bidirectional streams"
msgstr "جریانهای دوطرفه همزمان QUIC"
+msgid "Keep Alive"
+msgstr "زنده نگه داشتن اتصال"
+
msgid "QUIC KeepAlive interval"
msgstr "فاصله QUIC KeepAlive"
@@ -1369,6 +1372,9 @@ msgstr "زمانی استفاده میشود که لینک گره شامل ا
msgid "%s Node Use Type"
msgstr "نوع استفاده گره %s"
+msgid "Other"
+msgstr "سایر"
+
msgid "UDP relay mode"
msgstr "حالت بازپخش UDP"
@@ -1585,12 +1591,6 @@ msgstr "همزمانی XUDP Mux"
msgid "Padding"
msgstr "پدینگ (Padding)"
-msgid "Enable early data"
-msgstr "فعالسازی دادههای اولیه (Early Data)"
-
-msgid "Early data length"
-msgstr "طول دادههای اولیه"
-
msgid "Early data header name"
msgstr "نام هدر دادههای اولیه"
diff --git a/luci-app-passwall2/po/ru/passwall2.po b/luci-app-passwall2/po/ru/passwall2.po
index 700bedd5..97b3487f 100644
--- a/luci-app-passwall2/po/ru/passwall2.po
+++ b/luci-app-passwall2/po/ru/passwall2.po
@@ -605,6 +605,9 @@ msgstr "Размер окна приёма соединения QUIC"
msgid "QUIC concurrent bidirectional streams"
msgstr "Макс. количество одновременных двунаправленных потоков QUIC"
+msgid "Keep Alive"
+msgstr "Поддержание соединения"
+
msgid "QUIC KeepAlive interval"
msgstr "Интервал QUIC KeepAlive"
@@ -1370,6 +1373,9 @@ msgstr "Используется, если в ссылке на узел это
msgid "%s Node Use Type"
msgstr "Тип использования сервера %s"
+msgid "Other"
+msgstr "Другие"
+
msgid "UDP relay mode"
msgstr "Режим ретрансляции UDP"
@@ -1586,12 +1592,6 @@ msgstr "Макс. параллельных соединений XUDP"
msgid "Padding"
msgstr "Заполнение"
-msgid "Enable early data"
-msgstr "Включить 0-RTT данные"
-
-msgid "Early data length"
-msgstr "Макс. длина 0-RTT данных"
-
msgid "Early data header name"
msgstr "Имя HTTP-заголовка для 0-RTT"
diff --git a/luci-app-passwall2/po/zh-cn/passwall2.po b/luci-app-passwall2/po/zh-cn/passwall2.po
index 0017efa4..9dff3ec0 100644
--- a/luci-app-passwall2/po/zh-cn/passwall2.po
+++ b/luci-app-passwall2/po/zh-cn/passwall2.po
@@ -596,6 +596,9 @@ msgstr "QUIC 连接接收窗口"
msgid "QUIC concurrent bidirectional streams"
msgstr "QUIC 并发双向流的最大数量"
+msgid "Keep Alive"
+msgstr "保持连接"
+
msgid "QUIC KeepAlive interval"
msgstr "QUIC KeepAlive 间隔"
@@ -1361,6 +1364,9 @@ msgstr "当节点链接未包含该参数时,将使用此设置。"
msgid "%s Node Use Type"
msgstr "%s 节点使用类型"
+msgid "Other"
+msgstr "其他"
+
msgid "UDP relay mode"
msgstr "UDP中继模式"
@@ -1577,12 +1583,6 @@ msgstr "XUDP 最大并发连接数"
msgid "Padding"
msgstr "填充"
-msgid "Enable early data"
-msgstr "启用前置数据"
-
-msgid "Early data length"
-msgstr "前置数据最大长度"
-
msgid "Early data header name"
msgstr "前置数据 HTTP 头名"
diff --git a/luci-app-passwall2/po/zh-tw/passwall2.po b/luci-app-passwall2/po/zh-tw/passwall2.po
index 1d993c71..a0c9488b 100644
--- a/luci-app-passwall2/po/zh-tw/passwall2.po
+++ b/luci-app-passwall2/po/zh-tw/passwall2.po
@@ -596,6 +596,9 @@ msgstr "QUIC 連接接收窗口"
msgid "QUIC concurrent bidirectional streams"
msgstr "QUIC 並發双向流的最大數量"
+msgid "Keep Alive"
+msgstr "保持連線"
+
msgid "QUIC KeepAlive interval"
msgstr "QUIC KeepAlive 間隔"
@@ -1361,6 +1364,9 @@ msgstr "當節點連結未包含該參數時,將使用此設定。"
msgid "%s Node Use Type"
msgstr "%s 節點使用類型"
+msgid "Other"
+msgstr "其他"
+
msgid "UDP relay mode"
msgstr "UDP中继模式"
@@ -1577,12 +1583,6 @@ msgstr "XUDP 最大並發連接數"
msgid "Padding"
msgstr "填充"
-msgid "Enable early data"
-msgstr "啟用前置數据"
-
-msgid "Early data length"
-msgstr "前置數据最大长度"
-
msgid "Early data header name"
msgstr "前置數据 HTTP 头名"
diff --git a/luci-app-passwall2/root/usr/share/passwall2/subscribe.lua b/luci-app-passwall2/root/usr/share/passwall2/subscribe.lua
index 69ec808a..fdf27e82 100755
--- a/luci-app-passwall2/root/usr/share/passwall2/subscribe.lua
+++ b/luci-app-passwall2/root/usr/share/passwall2/subscribe.lua
@@ -100,6 +100,7 @@ local DEFAULT_TROJAN_TYPE = api.get_core("trojan_type", {{has_singbox,"sing-box"
local DEFAULT_VMESS_TYPE = api.get_core("vmess_type", {{has_xray,"xray"},{has_singbox,"sing-box"}})
local DEFAULT_VLESS_TYPE = api.get_core("vless_type", {{has_xray,"xray"},{has_singbox,"sing-box"}})
local DEFAULT_HYSTERIA2_TYPE = api.get_core("hysteria2_type", {{has_singbox,"sing-box"},{has_xray,"xray"}})
+local DEFAULT_OTHER_TYPE = api.get_core("other_type", {{has_singbox,"sing-box"},{has_xray,"xray"}})
local core_has = {
["xray"] = has_xray,
["sing-box"] = has_singbox,
@@ -536,6 +537,7 @@ local function processData(szType, content, add_mode, group, sub_cfg)
local sub_vmess_type = DEFAULT_VMESS_TYPE
local sub_vless_type = DEFAULT_VLESS_TYPE
local sub_hysteria2_type = DEFAULT_HYSTERIA2_TYPE
+ local sub_other_type = DEFAULT_OTHER_TYPE
local sub_hy_up_mbps, sub_hy_down_mbps
if sub_cfg then
if sub_cfg.allowInsecure and sub_cfg.allowInsecure ~= "1" then
@@ -561,6 +563,10 @@ local function processData(szType, content, add_mode, group, sub_cfg)
if hysteria2_type ~= "global" and core_has[hysteria2_type] then
sub_hysteria2_type = hysteria2_type
end
+ local other_type = sub_cfg.other_type or "global"
+ if other_type ~= "global" and core_has[other_type] then
+ sub_other_type = other_type
+ end
sub_hy_up_mbps = sub_cfg.hysteria_up_mbps
sub_hy_down_mbps = sub_cfg.hysteria_down_mbps
end
@@ -679,21 +685,6 @@ local function processData(szType, content, add_mode, group, sub_cfg)
if info.net == 'ws' then
result.ws_host = info.host
result.ws_path = info.path
- if result.type == "sing-box" and info.path then
- local ws_path_dat = split(info.path, "?")
- local ws_path = ws_path_dat[1]
- local ws_path_params = {}
- for _, v in pairs(split(ws_path_dat[2], '&')) do
- local t = split(v, '=')
- ws_path_params[t[1]] = t[2]
- end
- if ws_path_params.ed and tonumber(ws_path_params.ed) then
- result.ws_path = ws_path
- result.ws_enableEarlyData = "1"
- result.ws_maxEarlyData = tonumber(ws_path_params.ed)
- result.ws_earlyDataHeaderName = "Sec-WebSocket-Protocol"
- end
- end
end
if info.net == "http" then
if result.type == "Xray" then
@@ -936,21 +927,6 @@ local function processData(szType, content, add_mode, group, sub_cfg)
if params.type == 'ws' then
result.ws_host = params.host
result.ws_path = params.path
- if result.type == "sing-box" and params.path then
- local ws_path_dat = split(params.path, "%?")
- local ws_path = ws_path_dat[1]
- local ws_path_params = {}
- for _, v in pairs(split(ws_path_dat[2], '&')) do
- local t = split(v, '=')
- ws_path_params[t[1]] = t[2]
- end
- if ws_path_params.ed and tonumber(ws_path_params.ed) then
- result.ws_path = ws_path
- result.ws_enableEarlyData = "1"
- result.ws_maxEarlyData = tonumber(ws_path_params.ed)
- result.ws_earlyDataHeaderName = "Sec-WebSocket-Protocol"
- end
- end
end
if params.type == "http" then
if result.type == "sing-box" then
@@ -1152,21 +1128,6 @@ local function processData(szType, content, add_mode, group, sub_cfg)
if params.type == 'ws' then
result.ws_host = params.host
result.ws_path = params.path
- if result.type == "sing-box" and params.path then
- local ws_path_dat = split(params.path, "%?")
- local ws_path = ws_path_dat[1]
- local ws_path_params = {}
- for _, v in pairs(split(ws_path_dat[2], '&')) do
- local t = split(v, '=')
- ws_path_params[t[1]] = t[2]
- end
- if ws_path_params.ed and tonumber(ws_path_params.ed) then
- result.ws_path = ws_path
- result.ws_enableEarlyData = "1"
- result.ws_maxEarlyData = tonumber(ws_path_params.ed)
- result.ws_earlyDataHeaderName = "Sec-WebSocket-Protocol"
- end
- end
end
if params.type == "http" then
if result.type == "sing-box" then
@@ -1281,21 +1242,6 @@ local function processData(szType, content, add_mode, group, sub_cfg)
if params.type == 'ws' then
result.ws_host = params.host
result.ws_path = params.path
- if result.type == "sing-box" and params.path then
- local ws_path_dat = split(params.path, "%?")
- local ws_path = ws_path_dat[1]
- local ws_path_params = {}
- for _, v in pairs(split(ws_path_dat[2], '&')) do
- local t = split(v, '=')
- ws_path_params[t[1]] = t[2]
- end
- if ws_path_params.ed and tonumber(ws_path_params.ed) then
- result.ws_path = ws_path
- result.ws_enableEarlyData = "1"
- result.ws_maxEarlyData = tonumber(ws_path_params.ed)
- result.ws_earlyDataHeaderName = "Sec-WebSocket-Protocol"
- end
- end
end
if params.type == "http" then
if result.type == "sing-box" then
@@ -1682,6 +1628,115 @@ local function processData(szType, content, add_mode, group, sub_cfg)
result.snell_mode = params.mode or "default"
end
result.snell_reuse = (params.reuse == "1") and "1" or "0"
+ elseif szType == "wireguard" then
+ if sub_other_type == "sing-box" and has_singbox then
+ result.type = "sing-box"
+ elseif sub_other_type == "xray" and has_xray then
+ result.type = "Xray"
+ else
+ log(2, i18n.translatef("Skipping the %s node is due to incompatibility with the %s core program or incorrect node usage type settings.", "WireGuard", "Xray / sing-box"))
+ return nil
+ end
+ result.protocol = "wireguard"
+
+ local alias = ""
+ if content:find("#") then
+ local idx_sp = content:find("#")
+ alias = content:sub(idx_sp + 1, -1)
+ content = content:sub(0, idx_sp - 1)
+ end
+ result.remarks = UrlDecode(alias)
+ local contents = split(content, "@")
+ result.wireguard_secret_key = UrlDecode(contents[1]:gsub("%+", "%%2B"))
+ local query = split(contents[2], "%?")
+ local host_port = query[1]
+ local params = {}
+ for _, v in pairs(split(query[2], '&')) do
+ local s = v:find("=", 1, true)
+ if s and s > 1 then
+ params[UrlDecode(v:sub(1, s - 1)):lower()] = UrlDecode(v:sub(s + 1):gsub("%+", "%%2B"))
+ end
+ end
+ result.address, result.port = parse_host_port(host_port, "51820")
+ result.wireguard_public_key = params.publickey
+ result.wireguard_preSharedKey = params.presharedkey
+ result.wireguard_local_address = {}
+ for value in ((params.address or params.ip or "") .. "," .. (params.ipv6 or "")):gmatch("[^,%s]+") do
+ result.wireguard_local_address[#result.wireguard_local_address + 1] = value
+ end
+ result.wireguard_reserved = params.reserved
+ result.wireguard_mtu = params.mtu or "1420"
+ result.wireguard_keepAlive = params["persistent-keepalive"] or params.keepalive or "0"
+ result.use_finalmask = (params.fm and params.fm ~= "") and "1" or nil
+ result.finalmask = (params.fm and params.fm ~= "") and api.base64Encode(params.fm) or nil
+ elseif szType == "socks" or szType == "socks5" or szType == "socks5h" or szType == "http" or szType == "https" then
+ if sub_other_type == "sing-box" and has_singbox then
+ result.type = "sing-box"
+ elseif sub_other_type == "xray" and has_xray then
+ result.type = "Xray"
+ else
+ log(2, i18n.translatef("Skipping the %s node is due to incompatibility with the %s core program or incorrect node usage type settings.", szType, "Xray / sing-box"))
+ return nil
+ end
+ result.protocol = (szType == "http" or szType == "https") and "http" or "socks"
+
+ local alias = ""
+ if content:find("#") then
+ local idx_sp = content:find("#")
+ alias = content:sub(idx_sp + 1, -1)
+ content = content:sub(0, idx_sp - 1)
+ end
+ result.remarks = UrlDecode(alias)
+ local query = split(content, "%?")
+ local Info = query[1]
+ local base64_auth = false
+ if result.protocol == "socks" and not Info:find("@", 1, true) and not Info:find(":", 1, true) then
+ Info = base64Decode(UrlDecode(Info:gsub("%+", "%%2B")))
+ base64_auth = true
+ end
+ local auth, host_port = Info:match("^(.*)@(.*)$")
+ host_port = host_port or Info
+ auth = auth or ""
+ if result.protocol == "socks" and not auth:find(":", 1, true) then
+ auth = base64Decode(UrlDecode(auth:gsub("%+", "%%2B")))
+ base64_auth = true
+ end
+ local idx = auth:find(":", 1, true)
+ result.username = idx and auth:sub(1, idx - 1) or auth
+ result.password = idx and auth:sub(idx + 1) or ""
+ if not base64_auth then
+ result.username = UrlDecode(result.username:gsub("%+", "%%2B"))
+ result.password = UrlDecode(result.password:gsub("%+", "%%2B"))
+ end
+ local params = {}
+ for _, v in pairs(split(query[2], '&')) do
+ local s = v:find("=", 1, true)
+ if s and s > 1 then
+ params[UrlDecode(v:sub(1, s - 1)):lower()] = UrlDecode(v:sub(s + 1):gsub("%+", "%%2B"))
+ end
+ end
+ local port = result.protocol == "socks" and "1080" or (szType == "https" and "443" or "80")
+ result.address, result.port = parse_host_port(host_port, port)
+ result.transport = result.type == "Xray" and "raw" or "tcp"
+ result.uot = params.uot
+ result.tcp_fast_open = params.tfo
+ result.tls = szType == "https" and "1" or "0"
+ if result.tls == "1" then
+ result.tls_serverName = params.sni
+ result.alpn = params.alpn
+ local insecure = params.allowinsecure or params.insecure or params.allow_insecure
+ result.tls_allowInsecure = (insecure == "1" or insecure == "0") and insecure or (sub_allowinsecure and "1" or "0")
+ result.tls_pinSHA256 = params.pcs
+ result.tls_CertByName = params.vcn
+ if params.fp and params.fp ~= "" then
+ result.utls = "1"
+ result.fingerprint = params.fp
+ end
+ if params.ech and params.ech ~= "" then
+ result.ech = "1"
+ result.ech_config = params.ech
+ end
+ end
else
log(2, i18n.translatef("%s type node subscriptions are not currently supported, skip this node.", szType))
return nil
diff --git a/v2ray-geodata/Makefile b/v2ray-geodata/Makefile
index 692d6d32..7ecba74a 100644
--- a/v2ray-geodata/Makefile
+++ b/v2ray-geodata/Makefile
@@ -21,13 +21,13 @@ define Download/geoip
HASH:=eedb24b3b9fcba27e06b92c0428dab90d9496083091ae4cf39e11387fd99d0e2
endef
-GEOSITE_VER:=20261110004853
+GEOSITE_VER:=20261110034535
GEOSITE_FILE:=dlc.dat.$(GEOSITE_VER)
define Download/geosite
URL:=https://github.com/v2fly/domain-list-community/releases/download/$(GEOSITE_VER)/
URL_FILE:=dlc.dat
FILE:=$(GEOSITE_FILE)
- HASH:=5ce9f05c8968772940e29b7898adf232464a2a2a820252d17c5b33ffaecd1da9
+ HASH:=6cc4decf520dbfd96e1e3750661976eb31d2a5381090b8768cb2ab1528201fcf
endef
GEOSITE_IRAN_VER:=202610230802