💐 Sync 2025-11-05 00:13:10

This commit is contained in:
actions-user
2025-11-05 00:13:10 +08:00
parent 251207fb9c
commit e112520fb4
24 changed files with 4104 additions and 3316 deletions

View File

@@ -7,6 +7,8 @@ PKG_VERSION:=190
PKG_RELEASE:=3
PKG_CONFIG_DEPENDS:= \
CONFIG_PACKAGE_$(PKG_NAME)_Iptables_Transparent_Proxy \
CONFIG_PACKAGE_$(PKG_NAME)_Nftables_Transparent_Proxy \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_NONE_V2RAY \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_V2ray \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Xray \
@@ -39,9 +41,9 @@ PKG_CONFIG_DEPENDS:= \
LUCI_TITLE:=SS/SSR/V2Ray/Trojan/NaiveProxy/Tuic/ShadowTLS/Hysteria/Socks5/Tun LuCI interface
LUCI_PKGARCH:=all
LUCI_DEPENDS:= \
+coreutils +coreutils-base64 +dns2tcp +dnsmasq-full +@PACKAGE_dnsmasq_full_ipset +ipset +kmod-ipt-nat +jq \
+ip-full +iptables +iptables-mod-tproxy +lua +lua-neturl +libuci-lua +microsocks \
+tcping +resolveip +shadowsocksr-libev-ssr-check +curl +nping \
+coreutils +coreutils-base64 +dns2tcp +dnsmasq-full \
+jq +ip-full +lua +lua-neturl +libuci-lua +microsocks \
+tcping +resolveip +curl +nping \
+PACKAGE_$(PKG_NAME)_INCLUDE_V2ray:curl \
+PACKAGE_$(PKG_NAME)_INCLUDE_V2ray:v2ray-core \
+PACKAGE_$(PKG_NAME)_INCLUDE_Xray:curl \
@@ -68,14 +70,40 @@ LUCI_DEPENDS:= \
+PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Xray_Plugin:xray-plugin \
+PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Libev_Client:shadowsocksr-libev-ssr-local \
+PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Libev_Client:shadowsocksr-libev-ssr-redir \
+PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Libev_Server:shadowsocksr-libev-ssr-server \
+PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Libev_Client:shadowsocksr-libev-ssr-redir \
+PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Libev_Server:shadowsocksr-libev-ssr-check \
+PACKAGE_$(PKG_NAME)_INCLUDE_Trojan:trojan
define Package/$(PKG_NAME)/config
select PACKAGE_luci-lib-ipkg if PACKAGE_$(PKG_NAME)
select PACKAGE_luci-lua-runtime if PACKAGE_$(PKG_NAME)
choice
prompt "Transparent Proxy Backend"
default PACKAGE_$(PKG_NAME)_Iptables_Transparent_Proxy if ! PACKAGE_firewall4
default PACKAGE_$(PKG_NAME)_Nftables_Transparent_Proxy if PACKAGE_firewall4
config PACKAGE_$(PKG_NAME)_Iptables_Transparent_Proxy
bool "Iptables Transparent Proxy"
select PACKAGE_dnsmasq_full_ipset
select PACKAGE_ipset
select PACKAGE_iptables
select PACKAGE_iptables-zz-legacy
select PACKAGE_iptables-mod-conntrack-extra
select PACKAGE_iptables-mod-iprange
select PACKAGE_iptables-mod-socket
select PACKAGE_iptables-mod-tproxy
select PACKAGE_kmod-ipt-nat
config PACKAGE_$(PKG_NAME)_Nftables_Transparent_Proxy
bool "Nftables Transparent Proxy"
select PACKAGE_dnsmasq_full_nftset
select PACKAGE_nftables
select PACKAGE_kmod-nft-socket
select PACKAGE_kmod-nft-tproxy
select PACKAGE_kmod-nft-nat
endchoice
choice
prompt "Shadowsocks Client Selection"
default PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Rust_Client if aarch64||arm||i386||mips||mipsel||x86_64

View File

@@ -28,8 +28,9 @@ function index()
entry({"admin", "services", "shadowsocksr", "reset"}, call("act_reset"))
entry({"admin", "services", "shadowsocksr", "restart"}, call("act_restart"))
entry({"admin", "services", "shadowsocksr", "delete"}, call("act_delete"))
--[[Backup]]
--[[Backup]]
entry({"admin", "services", "shadowsocksr", "backup"}, call("create_backup")).leaf = true
end
function subscribe()
@@ -46,40 +47,74 @@ function act_status()
end
function act_ping()
local e = {}
local domain = luci.http.formvalue("domain")
local port = luci.http.formvalue("port")
local transport = luci.http.formvalue("transport")
local wsPath = luci.http.formvalue("wsPath")
local tls = luci.http.formvalue("tls")
e.index = luci.http.formvalue("index")
local iret = luci.sys.call("ipset add ss_spec_wan_ac " .. domain .. " 2>/dev/null")
if transport == "ws" then
local prefix = tls=='1' and "https://" or "http://"
local address = prefix..domain..':'..port..wsPath
local result = luci.sys.exec("curl --http1.1 -m 2 -ksN -o /dev/null -w 'time_connect=%{time_connect}\nhttp_code=%{http_code}' -H 'Connection: Upgrade' -H 'Upgrade: websocket' -H 'Sec-WebSocket-Key: SGVsbG8sIHdvcmxkIQ==' -H 'Sec-WebSocket-Version: 13' "..address)
e.socket = string.match(result,"http_code=(%d+)")=="101"
e.ping = tonumber(string.match(result, "time_connect=(%d+.%d%d%d)"))*1000
else
local socket = nixio.socket("inet", "stream")
socket:setopt("socket", "rcvtimeo", 3)
socket:setopt("socket", "sndtimeo", 3)
e.socket = socket:connect(domain, port)
socket:close()
e.ping = luci.sys.exec(string.format("echo -n $(tcping -q -c 1 -i 1 -t 2 -p %s %s 2>&1 | grep -o 'time=[0-9]*' | awk -F '=' '{print $2}') 2>/dev/null", port, domain))
if (e.ping == "") then
e.ping = luci.sys.exec("echo -n $(ping -c 1 -W 1 %q 2>&1 | grep -o 'time=[0-9]*' | awk -F '=' '{print $2}') 2>/dev/null" % domain)
if (e.ping == "") then
-- UDP ping test using nping
e.ping = luci.sys.exec(string.format("echo -n $(nping --udp -c 1 -p %s %s 2>/dev/null | grep -o 'Avg rtt: [0-9.]*ms' | awk '{print $3}' | sed 's/ms//' | head -1) 2>/dev/null", port, domain))
end
end
end
if (iret == 0) then
luci.sys.call(" ipset del ss_spec_wan_ac " .. domain)
end
luci.http.prepare_content("application/json")
luci.http.write_json(e)
local e = {}
local domain = luci.http.formvalue("domain")
local port = tonumber(luci.http.formvalue("port") or 0)
local transport = luci.http.formvalue("transport")
local wsPath = luci.http.formvalue("wsPath") or ""
local tls = luci.http.formvalue("tls")
e.index = luci.http.formvalue("index")
local use_nft = luci.sys.call("command -v nft >/dev/null") == 0
local iret = false
if use_nft then
iret = luci.sys.call("nft add element inet ss_spec ss_spec_wan_ac { " .. domain .. " } 2>/dev/null") == 0
else
iret = luci.sys.call("ipset add ss_spec_wan_ac " .. domain .. " 2>/dev/null") == 0
end
if transport == "ws" then
local prefix = tls == '1' and "https://" or "http://"
local address = prefix .. domain .. ':' .. port .. wsPath
local result = luci.sys.exec(
"curl --http1.1 -m 2 -ksN -o /dev/null " ..
"-w 'time_connect=%{time_connect}\nhttp_code=%{http_code}' " ..
"-H 'Connection: Upgrade' -H 'Upgrade: websocket' " ..
"-H 'Sec-WebSocket-Key: SGVsbG8sIHdvcmxkIQ==' " ..
"-H 'Sec-WebSocket-Version: 13' " .. address
)
e.socket = string.match(result,"http_code=(%d+)") == "101"
local ping_time = tonumber(string.match(result, "time_connect=(%d+.%d%d%d)"))
e.ping = ping_time and ping_time * 1000 or nil
else
-- TCP ping
local socket = nixio.socket("inet", "stream")
socket:setopt("socket", "rcvtimeo", 3)
socket:setopt("socket", "sndtimeo", 3)
e.socket = socket:connect(domain, port)
socket:close()
e.ping = tonumber(luci.sys.exec(string.format(
"tcping -q -c 1 -i 1 -t 2 -p %d %s 2>/dev/null | grep -o 'time=[0-9]*' | awk -F '=' '{print $2}'",
port, domain
)))
if not e.ping then
e.ping = tonumber(luci.sys.exec(string.format(
"ping -c 1 -W 1 %s 2>/dev/null | grep -o 'time=[0-9]*' | awk -F '=' '{print $2}'",
domain
)))
end
if not e.ping then
e.ping = tonumber(luci.sys.exec(string.format(
"nping --udp -c 1 -p %d %s 2>/dev/null | grep -o 'Avg rtt: [0-9.]*ms' | awk '{print $3}' | sed 's/ms//' | head -1",
port, domain
)))
end
end
if iret then
if use_nft then
luci.sys.call("nft delete element inet ss_spec ss_spec_wan_ac { " .. domain .. " } 2>/dev/null")
else
luci.sys.call("ipset del ss_spec_wan_ac " .. domain .. " 2>/dev/null")
end
end
luci.http.prepare_content("application/json")
luci.http.write_json(e)
end
function check_status()
@@ -101,28 +136,46 @@ function check_port()
local s
local server_name = ""
local uci = require "luci.model.uci".cursor()
local iret = 1
local use_nft = luci.sys.call("command -v nft >/dev/null") == 0
uci:foreach("shadowsocksr", "servers", function(s)
if s.alias then
server_name = s.alias
elseif s.server and s.server_port then
server_name = "%s:%s" % {s.server, s.server_port}
server_name = s.server .. ":" .. s.server_port
end
iret = luci.sys.call("ipset add ss_spec_wan_ac " .. s.server .. " 2>/dev/null")
socket = nixio.socket("inet", "stream")
-- 临时加入 set
local iret = false
if use_nft then
iret = luci.sys.call("nft add element inet ss_spec ss_spec_wan_ac { " .. s.server .. " } 2>/dev/null") == 0
else
iret = luci.sys.call("ipset add ss_spec_wan_ac " .. s.server .. " 2>/dev/null") == 0
end
-- TCP 测试
local socket = nixio.socket("inet", "stream")
socket:setopt("socket", "rcvtimeo", 3)
socket:setopt("socket", "sndtimeo", 3)
ret = socket:connect(s.server, s.server_port)
if tostring(ret) == "true" then
socket:close()
retstring = retstring .. "<font><b style='color:green'>[" .. server_name .. "] OK.</b></font><br />"
local ret = socket:connect(s.server, s.server_port)
socket:close()
if ret then
retstring = retstring .. string.format("<font><b style='color:green'>[%s] OK.</b></font><br />", server_name)
else
retstring = retstring .. "<font><b style='color:red'>[" .. server_name .. "] Error.</b></font><br />"
retstring = retstring .. string.format("<font><b style='color:red'>[%s] Error.</b></font><br />", server_name)
end
if iret == 0 then
luci.sys.call("ipset del ss_spec_wan_ac " .. s.server)
-- 删除临时 set
if iret then
if use_nft then
luci.sys.call("nft delete element inet ss_spec ss_spec_wan_ac { " .. s.server .. " } 2>/dev/null")
else
luci.sys.call("ipset del ss_spec_wan_ac " .. s.server)
end
end
end)
luci.http.prepare_content("application/json")
luci.http.write_json({ret = retstring})
end

View File

@@ -4,14 +4,22 @@ local uci = require "luci.model.uci".cursor()
-- 获取 LAN IP 地址
function lanip()
local lan_ip
lan_ip = luci.sys.exec("uci -q get network.lan.ipaddr 2>/dev/null | awk -F '/' '{print $1}' | tr -d '\n'")
-- 尝试从 UCI 直接读取
lan_ip = luci.sys.exec("uci -q get network.lan.ipaddr 2>/dev/null | awk -F'/' '{print $1}' | tr -d '\\n'")
-- 尝试从 LAN 接口信息中读取(优先 ifname再 fallback 到 device
if not lan_ip or lan_ip == "" then
lan_ip = luci.sys.exec("ip address show $(uci -q -p /tmp/state get network.lan.ifname || uci -q -p /tmp/state get network.lan.device) | grep -w 'inet' | grep -Eo 'inet [0-9\.]+' | awk '{print $2}' | head -1 | tr -d '\n'")
lan_ip = luci.sys.exec([[
ip -4 addr show $(uci -q -p /tmp/state get network.lan.ifname || uci -q -p /tmp/state get network.lan.device) 2>/dev/null \
| grep -w 'inet' | awk '{print $2}' | cut -d'/' -f1 | grep -v '^127\.' | head -n1 | tr -d '\n']])
end
-- 取任意一个 global IPv4 地址
if not lan_ip or lan_ip == "" then
lan_ip = luci.sys.exec("ip addr show | grep -w 'inet' | grep 'global' | grep -Eo 'inet [0-9\.]+' | awk '{print $2}' | head -n 1 | tr -d '\n'")
lan_ip = luci.sys.exec([[
ip -4 addr show scope global 2>/dev/null \
| grep -w 'inet' | awk '{print $2}' | cut -d'/' -f1 | grep -v '^127\.' | head -n1 | tr -d '\n']])
end
return lan_ip

View File

@@ -264,7 +264,7 @@ s = m:section(NamedSection, sid, "servers")
s.anonymous = true
s.addremove = false
o = s:option(DummyValue, "ssr_url", "SS/SSR/V2RAY/TROJAN/HYSTERIA2 URL")
o = s:option(DummyValue, "ssr_url", "SS/SSR/V2RAY/TROJAN/TUIC/HYSTERIA2 URL")
o.rawhtml = true
o.template = "shadowsocksr/ssrurl"
o.value = sid
@@ -1285,15 +1285,26 @@ o = s:option(ListValue, "tuic_alpn", translate("TUIC ALPN"))
o.default = ""
o:value("", translate("Default"))
o:value("h3")
o:value("h2")
o:value("h3,h2")
o:value("spdy/3.1")
o:value("h3,spdy/3.1")
o:depends("type", "tuic")
-- IP STACK PREFERENCE
o = s:option(ListValue, "ipstack_prefer", translate("IP Stack Preference"))
o.default = ""
o:value("", translate("Default"))
o:value("v4first")
o:value("v6first")
o:depends("tuic_dual_stack", true)
-- [[ allowInsecure ]]--
o = s:option(Flag, "insecure", translate("allowInsecure"))
o.rmempty = false
o:depends("tls", true)
o:depends("type", "hysteria2")
o:depends("type", "tuic")
o.description = translate("If true, allowss insecure connection at TLS client, e.g., TLS server uses unverifiable certificates.")
-- [[ Hysteria2 TLS pinSHA256 ]] --

View File

@@ -8,14 +8,22 @@ local uci = require "luci.model.uci".cursor()
-- 获取 LAN IP 地址
function lanip()
local lan_ip
lan_ip = luci.sys.exec("uci -q get network.lan.ipaddr 2>/dev/null | awk -F '/' '{print $1}' | tr -d '\n'")
-- 尝试从 UCI 直接读取
lan_ip = luci.sys.exec("uci -q get network.lan.ipaddr 2>/dev/null | awk -F'/' '{print $1}' | tr -d '\\n'")
-- 尝试从 LAN 接口信息中读取(优先 ifname再 fallback 到 device
if not lan_ip or lan_ip == "" then
lan_ip = luci.sys.exec("ip address show $(uci -q -p /tmp/state get network.lan.ifname || uci -q -p /tmp/state get network.lan.device) | grep -w 'inet' | grep -Eo 'inet [0-9\.]+' | awk '{print $2}' | head -1 | tr -d '\n'")
lan_ip = luci.sys.exec([[
ip -4 addr show $(uci -q -p /tmp/state get network.lan.ifname || uci -q -p /tmp/state get network.lan.device) 2>/dev/null \
| grep -w 'inet' | awk '{print $2}' | cut -d'/' -f1 | grep -v '^127\.' | head -n1 | tr -d '\n']])
end
-- 取任意一个 global IPv4 地址
if not lan_ip or lan_ip == "" then
lan_ip = luci.sys.exec("ip addr show | grep -w 'inet' | grep 'global' | grep -Eo 'inet [0-9\.]+' | awk '{print $2}' | head -n 1 | tr -d '\n'")
lan_ip = luci.sys.exec([[
ip -4 addr show scope global 2>/dev/null \
| grep -w 'inet' | awk '{print $2}' | cut -d'/' -f1 | grep -v '^127\.' | head -n1 | tr -d '\n']])
end
return lan_ip

View File

@@ -75,9 +75,9 @@ function export_ssr_url(btn, urlname, sid) {
textarea.select();
try {
document.execCommand("copy"); // Security exception may be thrown by some browsers.
s.innerHTML = "<font color='green'><%:Copy SSR to clipboard successfully.%></font>";
s.innerHTML = "<font style=\'color:green\'><%:Copy SSR to clipboard successfully.%></font>";
} catch (ex) {
s.innerHTML = "<font color='red'><%:Unable to copy SSR to clipboard.%></font>";
s.innerHTML = "<font style=\'color:red\'><%:Unable to copy SSR to clipboard.%></font>";
} finally {
document.body.removeChild(textarea);
}
@@ -89,7 +89,7 @@ function import_ssr_url(btn, urlname, sid) {
if (!s) return false;
var ssrurl = prompt("<%:Paste sharing link here%>", "");
if (ssrurl == null || ssrurl == "") {
s.innerHTML = "<font color='red'><%:User cancelled.%></font>";
s.innerHTML = "<font style=\'color:red\'><%:User cancelled.%></font>";
return false;
}
s.innerHTML = "";
@@ -97,18 +97,6 @@ function import_ssr_url(btn, urlname, sid) {
ssrurl = ssrurl.replace(/&([a-zA-Z]+);/g, '&').replace(/\s*#\s*/, '#').trim(); //一些奇葩的链接用"&amp;"当做"&""#"前后带空格
var ssu = ssrurl.split('://');
//console.log(ssu.length);
if (ssu[0] === "ss") {
var queryStr = "";
if (ssu[1].indexOf("?") > -1) {
queryStr = ssu[1].split("?")[1]; // 提取 ? 后面的参数
}
var params = new URLSearchParams(queryStr);
if (params.get("type")) {
// 替换协议头
ssrurl = ssrurl.replace(/^ss:\/\//i, "shadowsocks://");
var ssu = ssrurl.split('://');
}
}
var event = document.createEvent("HTMLEvents");
event.initEvent("change", true, true);
switch (ssu[0]) {
@@ -189,136 +177,281 @@ function import_ssr_url(btn, urlname, sid) {
// 再分离 ? 或 /?(参数)
var queryIndex = (url0 = url0.replace('/?', '?')).indexOf("?");
var queryStr = "";
var query = "";
if (queryIndex >= 0) {
queryStr = url0.substring(queryIndex + 1);
query = url0.substring(queryIndex + 1);
url0 = url0.substring(0, queryIndex);
}
var params = Object.fromEntries(new URLSearchParams(queryStr));
var params = Object.fromEntries(new URLSearchParams(query));
// 判断是否 SIP002 格式(即含 @
if (url0.indexOf("@") !== -1) {
// === SIP002 格式 ===
var sipIndex = url0.indexOf("@");
// 先 URL 解码 base64 再解码
var userInfoB64 = decodeURIComponent(url0.substring(0, sipIndex));
var userInfo = b64decsafe(userInfoB64);
var userInfoSplitIndex = userInfo.indexOf(":");
if(userInfoSplitIndex < 0) {
// 格式错误
s.innerHTML = "<font style='color:red'><%:Userinfo format error.%></font>";
break;
}
var method = userInfo.substring(0, userInfoSplitIndex);
var password = userInfo.substring(userInfoSplitIndex + 1);
var serverPart = url0.substring(sipIndex + 1);
var serverInfo = serverPart.split(":");
if ( ! params.type) {
// 普通 SS 导入逻辑
// 判断是否 SIP002 格式(即含 @
if (url0.indexOf("@") !== -1) {
// === SIP002 格式 ===
var sipIndex = url0.indexOf("@");
// 先 URL 解码 base64 再解码
var userInfoB64 = decodeURIComponent(url0.substring(0, sipIndex));
var userInfo = b64decsafe(userInfoB64);
var userInfoSplitIndex = userInfo.indexOf(":");
if(userInfoSplitIndex < 0) {
// 格式错误
s.innerHTML = "<font style='color:red'><%:Userinfo format error.%></font>";
break;
}
var method = userInfo.substring(0, userInfoSplitIndex);
var password = userInfo.substring(userInfoSplitIndex + 1);
var serverPart = url0.substring(sipIndex + 1);
var serverInfo = serverPart.split(":");
var server = serverInfo[0];
var port = serverInfo[1];
var server = serverInfo[0];
var port = serverInfo[1];
var plugin = "", pluginOpts = "";
if (params.plugin) {
var pluginParams = decodeURIComponent(params.plugin).split(";");
plugin = pluginParams.shift();
pluginOpts = pluginParams.join(";");
}
} else {
// === Base64 SS2022 / 普通格式 的整体编码格式 ===
// 先 URL 解码整个字符串
var decodedUrl0 = decodeURIComponent(url0);
var sstr = b64decsafe(decodedUrl0);
if (!sstr) {
s.innerHTML = "<font style='color:red'><%:Base64 sstr failed.%></font>";
break;
}
// 支持 SS2022 / 普通格式
var regex2022 = /^([^:]+):([^:]+):([^@]+)@([^:]+):(\d+)$/;
var regexNormal = /^([^:]+):([^@]+)@([^:]+):(\d+)$/;
var m2022 = sstr.match(regex2022);
var mNormal = sstr.match(regexNormal);
if (m2022) {
var method = m2022[1];
var password = m2022[2] + ":" + m2022[3];
var server = m2022[4];
var port = m2022[5];
} else if (mNormal) {
var method = mNormal[1];
var password = mNormal[2];
var server = mNormal[3];
var port = mNormal[4];
var plugin = "", pluginOpts = "";
if (params.plugin) {
var pluginParams = decodeURIComponent(params.plugin).split(";");
plugin = pluginParams.shift();
pluginOpts = pluginParams.join(";");
}
} else {
s.innerHTML = "<font style='color:red'><%:SS URL base64 sstr format not recognized.%></font>";
break;
}
// === Base64 SS2022 / 普通格式 的整体编码格式 ===
// 先 URL 解码整个字符串
var decodedUrl0 = decodeURIComponent(url0);
var sstr = b64decsafe(decodedUrl0);
if (!sstr) {
s.innerHTML = "<font style='color:red'><%:Base64 sstr failed.%></font>";
break;
}
var plugin = "", pluginOpts = "";
if (params["shadow-tls"]) {
try {
var decoded_tls = JSON.parse(atob(decodeURIComponent(params["shadow-tls"])));
plugin = "shadow-tls";
var versionFlag = "";
if (decoded_tls.version && !isNaN(decoded_tls.version)) {
versionFlag = "v" + decoded_tls.version + "=1;";
// 支持 SS2022 / 普通格式
var regex2022 = /^([^:]+):([^:]+):([^@]+)@([^:]+):(\d+)$/;
var regexNormal = /^([^:]+):([^@]+)@([^:]+):(\d+)$/;
var m2022 = sstr.match(regex2022);
var mNormal = sstr.match(regexNormal);
if (m2022) {
var method = m2022[1];
var password = m2022[2] + ":" + m2022[3];
var server = m2022[4];
var port = m2022[5];
} else if (mNormal) {
var method = mNormal[1];
var password = mNormal[2];
var server = mNormal[3];
var port = mNormal[4];
} else {
s.innerHTML = "<font style='color:red'><%:SS URL base64 sstr format not recognized.%></font>";
break;
}
var plugin = "", pluginOpts = "";
if (params["shadow-tls"]) {
try {
var decoded_tls = JSON.parse(atob(decodeURIComponent(params["shadow-tls"])));
plugin = "shadow-tls";
var versionFlag = "";
if (decoded_tls.version && !isNaN(decoded_tls.version)) {
versionFlag = "v" + decoded_tls.version + "=1;";
}
pluginOpts = versionFlag + "host=" + (decoded_tls.host || "") + ";passwd=" + (decoded_tls.password || "");
} catch (e) {
console.log("shadow-tls decode failed:", e);
}
pluginOpts = versionFlag + "host=" + (decoded_tls.host || "") + ";passwd=" + (decoded_tls.password || "");
} catch (e) {
console.log("shadow-tls decode failed:", e);
}
}
}
// 判断密码是否经过url编码
const isURLEncodedPassword = function(pwd) {
if (!/%[0-9A-Fa-f]{2}/.test(pwd)) return false;
// 判断密码是否经过url编码
const isURLEncodedPassword = function(pwd) {
if (!/%[0-9A-Fa-f]{2}/.test(pwd)) return false;
try {
const decoded = decodeURIComponent(pwd.replace(/\+/g, "%20"));
const reencoded = encodeURIComponent(decoded);
return reencoded === pwd;
} catch (e) {
return false;
}
}
if (isURLEncodedPassword(password)) {
password = decodeURIComponent(password); // 解码URL编码
} else {
password = password; // 保持原始值
}
// === 填充配置项 ===
var has_ss_type = (ss_type === "ss-rust") ? "ss-rust" : "ss-libev";
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].value = ssu[0];
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].dispatchEvent(event);
document.getElementsByName('cbid.shadowsocksr.' + sid + '.has_ss_type')[0].value = has_ss_type;
document.getElementsByName('cbid.shadowsocksr.' + sid + '.has_ss_type')[0].dispatchEvent(event);
document.getElementsByName('cbid.shadowsocksr.' + sid + '.server')[0].value = server;
document.getElementsByName('cbid.shadowsocksr.' + sid + '.server_port')[0].value = port;
document.getElementsByName('cbid.shadowsocksr.' + sid + '.password')[0].value = password || "";
document.getElementsByName('cbid.shadowsocksr.' + sid + '.encrypt_method_ss')[0].value = method;
document.getElementsByName('cbid.shadowsocksr.' + sid + '.encrypt_method_ss')[0].dispatchEvent(event);
if (plugin && plugin !== "none") {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.enable_plugin')[0].checked = true; // 设置 enable_plugin 为 true
document.getElementsByName('cbid.shadowsocksr.' + sid + '.enable_plugin')[0].dispatchEvent(event); // 触发事件
document.getElementsByName('cbid.shadowsocksr.' + sid + '.plugin')[0].value = plugin;
document.getElementsByName('cbid.shadowsocksr.' + sid + '.plugin')[0].dispatchEvent(event);
if (plugin !== undefined) {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.plugin_opts')[0].value = pluginOpts || "";
}
} else {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.enable_plugin')[0].checked = false;
}
if (param != undefined) {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.alias')[0].value = decodeURIComponent(param);
}
s.innerHTML = "<font style=\'color:green\'><%:Import configuration information successfully.%></font>";
return false;
} else {
try {
const decoded = decodeURIComponent(pwd.replace(/\+/g, "%20"));
const reencoded = encodeURIComponent(decoded);
return reencoded === pwd;
} catch (e) {
// Xray SS 导入逻辑
// 拆分 @,判断是否是 base64 userinfo 的格式
var parts = url0.split("@");
if (parts.length > 1) {
// @ 前是 base64(method:password),后面是 server:port?params
var userinfo = b64decsafe(parts[0]);
var sepIndex = userinfo.indexOf(":");
if (sepIndex > -1) {
method = userinfo.slice(0, sepIndex);
password = userinfo.slice(sepIndex + 1); //一些链接用明文uuid做密码
}
}
var url = new URL("http://" + url0 + (param ? "#" + encodeURIComponent(param) : ""));
} catch(e) {
alert(e);
return false;
}
}
if (isURLEncodedPassword(password)) {
password = decodeURIComponent(password); // 解码URL编码
} else {
password = password; // 保持原始值
}
// === 填充配置项 ===
var has_ss_type = (ss_type === "ss-rust") ? "ss-rust" : "ss-libev";
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].value = ssu[0];
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].dispatchEvent(event);
document.getElementsByName('cbid.shadowsocksr.' + sid + '.has_ss_type')[0].value = has_ss_type;
document.getElementsByName('cbid.shadowsocksr.' + sid + '.has_ss_type')[0].dispatchEvent(event);
document.getElementsByName('cbid.shadowsocksr.' + sid + '.server')[0].value = server;
document.getElementsByName('cbid.shadowsocksr.' + sid + '.server_port')[0].value = port;
document.getElementsByName('cbid.shadowsocksr.' + sid + '.password')[0].value = password || "";
document.getElementsByName('cbid.shadowsocksr.' + sid + '.encrypt_method_ss')[0].value = method;
document.getElementsByName('cbid.shadowsocksr.' + sid + '.encrypt_method_ss')[0].dispatchEvent(event);
if (plugin && plugin !== "none") {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.enable_plugin')[0].checked = true; // 设置 enable_plugin 为 true
document.getElementsByName('cbid.shadowsocksr.' + sid + '.enable_plugin')[0].dispatchEvent(event); // 触发事件
document.getElementsByName('cbid.shadowsocksr.' + sid + '.plugin')[0].value = plugin;
document.getElementsByName('cbid.shadowsocksr.' + sid + '.plugin')[0].dispatchEvent(event);
if (plugin !== undefined) {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.plugin_opts')[0].value = pluginOpts || "";
// Check if the elements exist before trying to modify them
function setElementValue(name, value) {
const element = document.getElementsByName(name)[0];
if (element) {
if (typeof value === 'boolean') {
element.checked = value;
} else {
element.value = value;
}
}
}
} else {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.enable_plugin')[0].checked = false;
}
function dispatchEventIfExists(name, event) {
const element = document.getElementsByName(name)[0];
if (element) {
element.dispatchEvent(event);
}
}
setElementValue('cbid.shadowsocksr.' + sid + '.alias', url.hash ? decodeURIComponent(url.hash.slice(1)) : "");
setElementValue('cbid.shadowsocksr.' + sid + '.type', "v2ray");
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.type', event);
setElementValue('cbid.shadowsocksr.' + sid + '.v2ray_protocol', "shadowsocks");
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.v2ray_protocol', event);
setElementValue('cbid.shadowsocksr.' + sid + '.server', url.hostname);
setElementValue('cbid.shadowsocksr.' + sid + '.server_port', url.port || "80");
setElementValue('cbid.shadowsocksr.' + sid + '.password', password || url.username);
setElementValue('cbid.shadowsocksr.' + sid + '.transport',
params.type === "http" ? "h2" :
(["xhttp", "splithttp"].includes(params.type) ? "xhttp" :
(["tcp", "raw"].includes(params.type) ? "raw" :
(params.type || "raw")))
);
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.transport', event);
setElementValue('cbid.shadowsocksr.' + sid + '.encrypt_method_ss', method || params.encryption || "none");
if ([ "tls", "xtls", "reality" ].includes(params.security)) {
setElementValue('cbid.shadowsocksr.' + sid + '.' + params.security, true);
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.' + params.security, event);
if (param != undefined) {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.alias')[0].value = decodeURIComponent(param);
if (params.security === "tls") {
if (params.ech && params.ech.trim() !== "") {
setElementValue('cbid.shadowsocksr.' + sid + '.enable_ech', true); // 设置 enable_ech 为 true
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.enable_ech', event); // 触发事件
setElementValue('cbid.shadowsocksr.' + sid + '.ech_config', params.ech || "");
}
if (params.allowInsecure === "1") {
setElementValue('cbid.shadowsocksr.' + sid + '.insecure', true); // 设置 insecure 为 true
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.insecure', event); // 触发事件
}
}
if (params.security === "reality") {
setElementValue('cbid.shadowsocksr.' + sid + '.reality_publickey', params.pbk ? decodeURIComponent(params.pbk) : "");
setElementValue('cbid.shadowsocksr.' + sid + '.reality_shortid', params.sid || "");
setElementValue('cbid.shadowsocksr.' + sid + '.reality_spiderx', params.spx ? decodeURIComponent(params.spx) : "");
if (params.pqv && params.pqv.trim() !== "") {
setElementValue('cbid.shadowsocksr.' + sid + '.enable_mldsa65verify', true); // 设置 enable_mldsa65verify 为 true
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.enable_mldsa65verify', event); // 触发事件
setElementValue('cbid.shadowsocksr.' + sid + '.reality_mldsa65verify', params.pqv || "");
}
}
setElementValue('cbid.shadowsocksr.' + sid + '.tls_flow', params.flow || "none");
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.tls_flow', event);
setElementValue('cbid.shadowsocksr.' + sid + '.tls_alpn', params.alpn || "");
setElementValue('cbid.shadowsocksr.' + sid + '.fingerprint', params.fp || "");
setElementValue('cbid.shadowsocksr.' + sid + '.tls_host', params.sni || "");
}
switch (params.type) {
case "ws":
if (params.security !== "tls") {
setElementValue('cbid.shadowsocksr.' + sid + '.ws_host', params.host ? decodeURIComponent(params.host) : "");
}
setElementValue('cbid.shadowsocksr.' + sid + '.ws_path', params.path ? decodeURIComponent(params.path) : "/");
break;
case "httpupgrade":
if (params.security !== "tls") {
setElementValue('cbid.shadowsocksr.' + sid + '.httpupgrade_host', params.host ? decodeURIComponent(params.host) : "");
}
setElementValue('cbid.shadowsocksr.' + sid + '.httpupgrade_path', params.path ? decodeURIComponent(params.path) : "/");
break;
case "xhttp":
case "splithttp":
if (params.security !== "tls") {
setElementValue('cbid.shadowsocksr.' + sid + '.xhttp_host', params.host ? decodeURIComponent(params.host) : "");
}
setElementValue('cbid.shadowsocksr.' + sid + '.xhttp_mode', params.mode || "auto");
setElementValue('cbid.shadowsocksr.' + sid + '.xhttp_path', params.path ? decodeURIComponent(params.path) : "/");
if (params.extra && params.extra.trim() !== "") {
setElementValue('cbid.shadowsocksr.' + sid + '.enable_xhttp_extra', true); // 设置 enable_xhttp_extra 为 true
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.enable_xhttp_extra', event); // 触发事件
setElementValue('cbid.shadowsocksr.' + sid + '.xhttp_extra', params.extra || "");
}
break;
case "kcp":
setElementValue('cbid.shadowsocksr.' + sid + '.kcp_guise', params.headerType || "none");
setElementValue('cbid.shadowsocksr.' + sid + '.seed', params.seed || "");
break;
case "http":
/* this is non-standard, bullshit */
case "h2":
setElementValue('cbid.shadowsocksr.' + sid + '.h2_host', params.host ? decodeURIComponent(params.host) : "");
setElementValue('cbid.shadowsocksr.' + sid + '.h2_path', params.path ? decodeURIComponent(params.path) : "");
break;
case "quic":
setElementValue('cbid.shadowsocksr.' + sid + '.quic_guise', params.headerType || "none");
setElementValue('cbid.shadowsocksr.' + sid + '.quic_security', params.quicSecurity || "none");
setElementValue('cbid.shadowsocksr.' + sid + '.quic_key', params.key || "");
break;
case "grpc":
setElementValue('cbid.shadowsocksr.' + sid + '.serviceName', params.serviceName || "");
setElementValue('cbid.shadowsocksr.' + sid + '.grpc_mode', params.mode || "gun");
break;
case "tcp":
case "raw":
setElementValue('cbid.shadowsocksr.' + sid + '.tcp_guise', params.headerType || "none");
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.tcp_guise', event);
if (params.headerType === "http") {
setElementValue('cbid.shadowsocksr.' + sid + '.http_host', params.host ? decodeURIComponent(params.host) : "");
setElementValue('cbid.shadowsocksr.' + sid + '.http_path', params.path ? decodeURIComponent(params.path) : "");
}
break;
}
s.innerHTML = "<font style=\'color:green\'><%:Import configuration information successfully.%></font>";
return false;
}
s.innerHTML = "<font style=\'color:green\'><%:Import configuration information successfully.%></font>";
return false;
case "ssr":
var sstr = b64decsafe((ssu[1] || "").replace(/#.*/, "").trim());
var ploc = sstr.indexOf("/?");
@@ -349,7 +482,7 @@ function import_ssr_url(btn, urlname, sid) {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.protocol_param')[0].value = dictvalue(pdict, 'protoparam');
var rem = pdict['remarks'];
if (typeof (rem) != 'undefined' && rem != '' && rem.length > 0) document.getElementsByName('cbid.shadowsocksr.' + sid + '.alias')[0].value = b64decutf8safe(rem);
s.innerHTML = "<font color='green'><%:Import configuration information successfully.%></font>";
s.innerHTML = "<font style=\'color:green\'><%:Import configuration information successfully.%></font>";
return false;
case "trojan":
try {
@@ -360,93 +493,117 @@ function import_ssr_url(btn, urlname, sid) {
return false;
}
document.getElementsByName('cbid.shadowsocksr.' + sid + '.alias')[0].value = url.hash ? decodeURIComponent(url.hash.slice(1)) : "";
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].value = "v2ray";
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].dispatchEvent(event);
document.getElementsByName('cbid.shadowsocksr.' + sid + '.v2ray_protocol')[0].value = "trojan";
document.getElementsByName('cbid.shadowsocksr.' + sid + '.v2ray_protocol')[0].dispatchEvent(event);
document.getElementsByName('cbid.shadowsocksr.' + sid + '.server')[0].value = url.hostname;
document.getElementsByName('cbid.shadowsocksr.' + sid + '.server_port')[0].value = url.port || "80";
document.getElementsByName('cbid.shadowsocksr.' + sid + '.password')[0].value = decodeURIComponent(url.username);
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls')[0].checked = true;
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls')[0].dispatchEvent(event);
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls_host')[0].value = params.get("sni");
if (params.get("allowInsecure") === "1") {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.insecure')[0].checked = true; // 设置 insecure 为 true
document.getElementsByName('cbid.shadowsocksr.' + sid + '.insecure')[0].dispatchEvent(event); // 触发事件
}
if (params.get("ech") && params.get("ech").trim() !== "") {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.enable_ech')[0].checked = true; // 设置 enable_ech 为 true
document.getElementsByName('cbid.shadowsocksr.' + sid + '.enable_ech')[0].dispatchEvent(event); // 触发事件
document.getElementsByName('cbid.shadowsocksr.' + sid + '.ech_config')[0].value = params.get("ech");
}
document.getElementsByName('cbid.shadowsocksr.' + sid + '.transport')[0].value =
params.get("type") == "http" ? "h2" :
(["xhttp", "splithttp"].includes(params.get("type")) ? "xhttp" :
(["tcp", "raw"].includes(params.get("type")) ? "raw" :
(params.get("type") || "raw")));
document.getElementsByName('cbid.shadowsocksr.' + sid + '.transport')[0].dispatchEvent(event);
if (params.get("security") === "tls") {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls_alpn')[0].value = params.get("alpn") || "";
document.getElementsByName('cbid.shadowsocksr.' + sid + '.fingerprint')[0].value = params.get("fp") || "";
}
switch (params.get("type")) {
case "ws":
if (params.get("security") !== "tls") {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.ws_host')[0].value = params.get("host") ? decodeURIComponent(params.get("host")) : "";
if (!params.get("type")) {
// 普通 Trojan 导入逻辑
document.getElementsByName('cbid.shadowsocksr.' + sid + '.alias')[0].value = url.hash ? decodeURIComponent(url.hash.slice(1)) : "";
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].value = "trojan";
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].dispatchEvent(event);
document.getElementsByName('cbid.shadowsocksr.' + sid + '.server')[0].value = url.hostname;
document.getElementsByName('cbid.shadowsocksr.' + sid + '.server_port')[0].value = url.port || "80";
document.getElementsByName('cbid.shadowsocksr.' + sid + '.password')[0].value = decodeURIComponent(url.username);
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls')[0].checked = true;
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls')[0].dispatchEvent(event);
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls_host')[0].value = params.get("peer") || params.get("sni");
if (params.get("allowInsecure") === "1") {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.insecure')[0].checked = true; // 设置 insecure 为 true
document.getElementsByName('cbid.shadowsocksr.' + sid + '.insecure')[0].dispatchEvent(event); // 触发事件
}
document.getElementsByName('cbid.shadowsocksr.' + sid + '.ws_path')[0].value = params.get("path") ? decodeURIComponent(params.get("path")) : "/";
break;
case "httpupgrade":
if (params.get("security") !== "tls") {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.httpupgrade_host')[0].value = params.get("host") ? decodeURIComponent(params.get("host")) : "";
if (params.get("tfo") === "1") {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.fast_open')[0].checked = true; // 设置 fast_open 为 true
document.getElementsByName('cbid.shadowsocksr.' + sid + '.fast_open')[0].dispatchEvent(event); // 触发事件
}
document.getElementsByName('cbid.shadowsocksr.' + sid + '.httpupgrade_path')[0].value = params.get("path") ? decodeURIComponent(params.get("path")) : "/";
break;
case "xhttp":
case "splithttp":
document.getElementsByName('cbid.shadowsocksr.' + sid + '.xhttp_host')[0].value = params.get("host") ? decodeURIComponent(params.get("host")) : "";
document.getElementsByName('cbid.shadowsocksr.' + sid + '.xhttp_mode')[0].value = params.get("mode") || "auto";
document.getElementsByName('cbid.shadowsocksr.' + sid + '.xhttp_path')[0].value = params.get("path") ? decodeURIComponent(params.get("path")) : "/";
if (params.get("extra") && params.get("extra").trim() !== "") {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.enable_xhttp_extra')[0].checked = true; // 设置 enable_xhttp_extra 为 true
document.getElementsByName('cbid.shadowsocksr.' + sid + '.enable_xhttp_extra')[0].dispatchEvent(event); // 触发事件
document.getElementsByName('cbid.shadowsocksr.' + sid + '.xhttp_extra')[0].value = params.get("extra") || "";
}
break;
case "kcp":
document.getElementsByName('cbid.shadowsocksr.' + sid + '.kcp_guise')[0].value = params.get("headerType") || "none";
document.getElementsByName('cbid.shadowsocksr.' + sid + '.seed')[0].value = params.get("seed") || "";
break;
case "http":
/* this is non-standard, bullshit */
case "h2":
document.getElementsByName('cbid.shadowsocksr.' + sid + '.h2_host')[0].value = params.get("host") ? decodeURIComponent(params.get("host")) : "";
document.getElementsByName('cbid.shadowsocksr.' + sid + '.h2_path')[0].value = params.get("path") ? decodeURIComponent(params.get("path")) : "";
break;
case "quic":
document.getElementsByName('cbid.shadowsocksr.' + sid + '.quic_guise')[0].value = params.get("headerType") || "none";
document.getElementsByName('cbid.shadowsocksr.' + sid + '.quic_security')[0].value = params.get("quicSecurity") || "none";
document.getElementsByName('cbid.shadowsocksr.' + sid + '.quic_key')[0].value = params.get("key") || "";
break;
case "grpc":
document.getElementsByName('cbid.shadowsocksr.' + sid + '.serviceName')[0].value = params.get("serviceName") || "";
document.getElementsByName('cbid.shadowsocksr.' + sid + '.grpc_mode')[0].value = params.get("mode") || "gun";
break;
case "raw":
case "tcp":
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tcp_guise')[0].value = params.get("headerType") || "none";
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tcp_guise')[0].dispatchEvent(event);
if (params.get("headerType") === "http") {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.http_host')[0].value = params.get("host") ? decodeURIComponent(params.get("host")) : "";
document.getElementsByName('cbid.shadowsocksr.' + sid + '.http_path')[0].value = params.get("path") ? decodeURIComponent(params.get("path")) : "";
}
break;
}
s.innerHTML = "<font color='green'><%:Import configuration information successfully.%></font>";
return false;
s.innerHTML = "<font style=\'color:green\'><%:Import configuration information successfully.%></font>";
return false;
} else {
// Xray Trojan 导入逻辑
document.getElementsByName('cbid.shadowsocksr.' + sid + '.alias')[0].value = url.hash ? decodeURIComponent(url.hash.slice(1)) : "";
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].value = "v2ray";
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].dispatchEvent(event);
document.getElementsByName('cbid.shadowsocksr.' + sid + '.v2ray_protocol')[0].value = "trojan";
document.getElementsByName('cbid.shadowsocksr.' + sid + '.v2ray_protocol')[0].dispatchEvent(event);
document.getElementsByName('cbid.shadowsocksr.' + sid + '.server')[0].value = url.hostname;
document.getElementsByName('cbid.shadowsocksr.' + sid + '.server_port')[0].value = url.port || "80";
document.getElementsByName('cbid.shadowsocksr.' + sid + '.password')[0].value = decodeURIComponent(url.username);
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls')[0].checked = true;
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls')[0].dispatchEvent(event);
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls_host')[0].value = params.get("sni");
if (params.get("allowInsecure") === "1") {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.insecure')[0].checked = true; // 设置 insecure 为 true
document.getElementsByName('cbid.shadowsocksr.' + sid + '.insecure')[0].dispatchEvent(event); // 触发事件
}
if (params.get("ech") && params.get("ech").trim() !== "") {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.enable_ech')[0].checked = true; // 设置 enable_ech 为 true
document.getElementsByName('cbid.shadowsocksr.' + sid + '.enable_ech')[0].dispatchEvent(event); // 触发事件
document.getElementsByName('cbid.shadowsocksr.' + sid + '.ech_config')[0].value = params.get("ech");
}
document.getElementsByName('cbid.shadowsocksr.' + sid + '.transport')[0].value =
params.get("type") == "http" ? "h2" :
(["xhttp", "splithttp"].includes(params.get("type")) ? "xhttp" :
(["tcp", "raw"].includes(params.get("type")) ? "raw" :
(params.get("type") || "raw")));
document.getElementsByName('cbid.shadowsocksr.' + sid + '.transport')[0].dispatchEvent(event);
if (params.get("security") === "tls") {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tls_alpn')[0].value = params.get("alpn") || "";
document.getElementsByName('cbid.shadowsocksr.' + sid + '.fingerprint')[0].value = params.get("fp") || "";
}
switch (params.get("type")) {
case "ws":
if (params.get("security") !== "tls") {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.ws_host')[0].value = params.get("host") ? decodeURIComponent(params.get("host")) : "";
}
document.getElementsByName('cbid.shadowsocksr.' + sid + '.ws_path')[0].value = params.get("path") ? decodeURIComponent(params.get("path")) : "/";
break;
case "httpupgrade":
if (params.get("security") !== "tls") {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.httpupgrade_host')[0].value = params.get("host") ? decodeURIComponent(params.get("host")) : "";
}
document.getElementsByName('cbid.shadowsocksr.' + sid + '.httpupgrade_path')[0].value = params.get("path") ? decodeURIComponent(params.get("path")) : "/";
break;
case "xhttp":
case "splithttp":
document.getElementsByName('cbid.shadowsocksr.' + sid + '.xhttp_host')[0].value = params.get("host") ? decodeURIComponent(params.get("host")) : "";
document.getElementsByName('cbid.shadowsocksr.' + sid + '.xhttp_mode')[0].value = params.get("mode") || "auto";
document.getElementsByName('cbid.shadowsocksr.' + sid + '.xhttp_path')[0].value = params.get("path") ? decodeURIComponent(params.get("path")) : "/";
if (params.get("extra") && params.get("extra").trim() !== "") {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.enable_xhttp_extra')[0].checked = true; // 设置 enable_xhttp_extra 为 true
document.getElementsByName('cbid.shadowsocksr.' + sid + '.enable_xhttp_extra')[0].dispatchEvent(event); // 触发事件
document.getElementsByName('cbid.shadowsocksr.' + sid + '.xhttp_extra')[0].value = params.get("extra") || "";
}
break;
case "kcp":
document.getElementsByName('cbid.shadowsocksr.' + sid + '.kcp_guise')[0].value = params.get("headerType") || "none";
document.getElementsByName('cbid.shadowsocksr.' + sid + '.seed')[0].value = params.get("seed") || "";
break;
case "http":
/* this is non-standard, bullshit */
case "h2":
document.getElementsByName('cbid.shadowsocksr.' + sid + '.h2_host')[0].value = params.get("host") ? decodeURIComponent(params.get("host")) : "";
document.getElementsByName('cbid.shadowsocksr.' + sid + '.h2_path')[0].value = params.get("path") ? decodeURIComponent(params.get("path")) : "";
break;
case "quic":
document.getElementsByName('cbid.shadowsocksr.' + sid + '.quic_guise')[0].value = params.get("headerType") || "none";
document.getElementsByName('cbid.shadowsocksr.' + sid + '.quic_security')[0].value = params.get("quicSecurity") || "none";
document.getElementsByName('cbid.shadowsocksr.' + sid + '.quic_key')[0].value = params.get("key") || "";
break;
case "grpc":
document.getElementsByName('cbid.shadowsocksr.' + sid + '.serviceName')[0].value = params.get("serviceName") || "";
document.getElementsByName('cbid.shadowsocksr.' + sid + '.grpc_mode')[0].value = params.get("mode") || "gun";
break;
case "raw":
case "tcp":
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tcp_guise')[0].value = params.get("headerType") || "none";
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tcp_guise')[0].dispatchEvent(event);
if (params.get("headerType") === "http") {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.http_host')[0].value = params.get("host") ? decodeURIComponent(params.get("host")) : "";
document.getElementsByName('cbid.shadowsocksr.' + sid + '.http_path')[0].value = params.get("path") ? decodeURIComponent(params.get("path")) : "";
}
break;
}
s.innerHTML = "<font style=\'color:green\'><%:Import configuration information successfully.%></font>";
return false;
}
case "vmess":
var sstr = b64DecodeUnicode((ssu[1] || "").replace(/#.*/, "").trim());
var ploc = sstr.indexOf("/?");
@@ -532,7 +689,7 @@ function import_ssr_url(btn, urlname, sid) {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.mux')[0].checked = true;
document.getElementsByName('cbid.shadowsocksr.' + sid + '.mux')[0].dispatchEvent(event);
}
s.innerHTML = "<font color='green'><%:Import configuration information successfully.%></font>";
s.innerHTML = "<font style=\'color:green\'><%:Import configuration information successfully.%></font>";
return false;
case "vless":
try {
@@ -664,153 +821,85 @@ function import_ssr_url(btn, urlname, sid) {
}
s.innerHTML = "<font style=\'color:green\'><%:Import configuration information successfully.%></font>";
return false;
case "shadowsocks":
try {
// 处理完整 ss:// 链接
var urlinfo = ssu[1];
// 拆分 @,判断是否是 base64 userinfo 的格式
var parts = urlinfo.split("@");
if (parts.length > 1) {
// @ 前是 base64(method:password),后面是 server:port?params
var userinfo = b64decsafe(parts[0]);
var sepIndex = userinfo.indexOf(":");
if (sepIndex > -1) {
method = userinfo.slice(0, sepIndex);
password = userinfo.slice(sepIndex + 1); //一些链接用明文uuid做密码
}
}
var url = new URL("http://" + urlinfo);
case "tuic":
var url0 = (ssu[1] || "");
var param = "";
var params = url.searchParams;
// 先分离 #alias
var hashIndex = url0.indexOf("#");
if (hashIndex >= 0) {
param = url0.substring(hashIndex + 1);
url0 = url0.substring(0, hashIndex);
}
} catch(e) {
alert(e);
return false;
// 再分离 ? 或 /?(参数)
var queryIndex = (url0 = url0.replace('/?', '?')).indexOf("?");
var query = "";
if (queryIndex >= 0) {
query = url0.substring(queryIndex + 1);
url0 = url0.substring(0, queryIndex);
}
// Check if the elements exist before trying to modify them
function setElementValue(name, value) {
const element = document.getElementsByName(name)[0];
if (element) {
if (typeof value === 'boolean') {
element.checked = value;
} else {
element.value = value;
}
}
}
function dispatchEventIfExists(name, event) {
const element = document.getElementsByName(name)[0];
if (element) {
element.dispatchEvent(event);
}
}
setElementValue('cbid.shadowsocksr.' + sid + '.alias', url.hash ? decodeURIComponent(url.hash.slice(1)) : "");
setElementValue('cbid.shadowsocksr.' + sid + '.type', "v2ray");
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.type', event);
setElementValue('cbid.shadowsocksr.' + sid + '.v2ray_protocol', "shadowsocks");
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.v2ray_protocol', event);
setElementValue('cbid.shadowsocksr.' + sid + '.server', url.hostname);
setElementValue('cbid.shadowsocksr.' + sid + '.server_port', url.port || "80");
setElementValue('cbid.shadowsocksr.' + sid + '.password', password || url.username);
setElementValue('cbid.shadowsocksr.' + sid + '.transport',
params.get("type") === "http" ? "h2" :
(["xhttp", "splithttp"].includes(params.get("type")) ? "xhttp" :
(["tcp", "raw"].includes(params.get("type")) ? "raw" :
(params.get("type") || "raw")))
);
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.transport', event);
setElementValue('cbid.shadowsocksr.' + sid + '.encrypt_method_ss', method || params.get("encryption") || "none");
if ([ "tls", "xtls", "reality" ].includes(params.get("security"))) {
setElementValue('cbid.shadowsocksr.' + sid + '.' + params.get("security"), true);
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.' + params.get("security"), event);
if (params.get("security") === "tls") {
if (params.get("ech") && params.get("ech").trim() !== "") {
setElementValue('cbid.shadowsocksr.' + sid + '.enable_ech', true); // 设置 enable_ech 为 true
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.enable_ech', event); // 触发事件
setElementValue('cbid.shadowsocksr.' + sid + '.ech_config', params.get("ech") || "");
}
if (params.get("allowInsecure") === "1") {
setElementValue('cbid.shadowsocksr.' + sid + '.insecure', true); // 设置 insecure 为 true
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.insecure', event); // 触发事件
}
}
if (params.get("security") === "reality") {
setElementValue('cbid.shadowsocksr.' + sid + '.reality_publickey', params.get("pbk") ? decodeURIComponent(params.get("pbk")) : "");
setElementValue('cbid.shadowsocksr.' + sid + '.reality_shortid', params.get("sid") || "");
setElementValue('cbid.shadowsocksr.' + sid + '.reality_spiderx', params.get("spx") ? decodeURIComponent(params.get("spx")) : "");
if (params.get("pqv") && params.get("pqv").trim() !== "") {
setElementValue('cbid.shadowsocksr.' + sid + '.enable_mldsa65verify', true); // 设置 enable_mldsa65verify 为 true
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.enable_mldsa65verify', event); // 触发事件
setElementValue('cbid.shadowsocksr.' + sid + '.reality_mldsa65verify', params.get("pqv") || "");
}
}
setElementValue('cbid.shadowsocksr.' + sid + '.tls_flow', params.get("flow") || "none");
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.tls_flow', event);
var params = Object.fromEntries(new URLSearchParams(query));
setElementValue('cbid.shadowsocksr.' + sid + '.tls_alpn', params.get("alpn") || "");
setElementValue('cbid.shadowsocksr.' + sid + '.fingerprint', params.get("fp") || "");
setElementValue('cbid.shadowsocksr.' + sid + '.tls_host', params.get("sni") || "");
}
switch (params.get("type")) {
case "ws":
if (params.get("security") !== "tls") {
setElementValue('cbid.shadowsocksr.' + sid + '.ws_host', params.get("host") ? decodeURIComponent(params.get("host")) : "");
}
setElementValue('cbid.shadowsocksr.' + sid + '.ws_path', params.get("path") ? decodeURIComponent(params.get("path")) : "/");
break;
case "httpupgrade":
if (params.get("security") !== "tls") {
setElementValue('cbid.shadowsocksr.' + sid + '.httpupgrade_host', params.get("host") ? decodeURIComponent(params.get("host")) : "");
}
setElementValue('cbid.shadowsocksr.' + sid + '.httpupgrade_path', params.get("path") ? decodeURIComponent(params.get("path")) : "/");
break;
case "xhttp":
case "splithttp":
if (params.get("security") !== "tls") {
setElementValue('cbid.shadowsocksr.' + sid + '.xhttp_host', params.get("host") ? decodeURIComponent(params.get("host")) : "");
}
setElementValue('cbid.shadowsocksr.' + sid + '.xhttp_mode', params.get("mode") || "auto");
setElementValue('cbid.shadowsocksr.' + sid + '.xhttp_path', params.get("path") ? decodeURIComponent(params.get("path")) : "/");
if (params.get("extra") && params.get("extra").trim() !== "") {
setElementValue('cbid.shadowsocksr.' + sid + '.enable_xhttp_extra', true); // 设置 enable_xhttp_extra 为 true
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.enable_xhttp_extra', event); // 触发事件
setElementValue('cbid.shadowsocksr.' + sid + '.xhttp_extra', params.get("extra") || "");
}
break;
case "kcp":
setElementValue('cbid.shadowsocksr.' + sid + '.kcp_guise', params.get("headerType") || "none");
setElementValue('cbid.shadowsocksr.' + sid + '.seed', params.get("seed") || "");
break;
case "http":
/* this is non-standard, bullshit */
case "h2":
setElementValue('cbid.shadowsocksr.' + sid + '.h2_host', params.get("host") ? decodeURIComponent(params.get("host")) : "");
setElementValue('cbid.shadowsocksr.' + sid + '.h2_path', params.get("path") ? decodeURIComponent(params.get("path")) : "");
break;
case "quic":
setElementValue('cbid.shadowsocksr.' + sid + '.quic_guise', params.get("headerType") || "none");
setElementValue('cbid.shadowsocksr.' + sid + '.quic_security', params.get("quicSecurity") || "none");
setElementValue('cbid.shadowsocksr.' + sid + '.quic_key', params.get("key") || "");
break;
case "grpc":
setElementValue('cbid.shadowsocksr.' + sid + '.serviceName', params.get("serviceName") || "");
setElementValue('cbid.shadowsocksr.' + sid + '.grpc_mode', params.get("mode") || "gun");
break;
case "tcp":
case "raw":
setElementValue('cbid.shadowsocksr.' + sid + '.tcp_guise', params.get("headerType") || "none");
dispatchEventIfExists('cbid.shadowsocksr.' + sid + '.tcp_guise', event);
if (params.get("headerType") === "http") {
setElementValue('cbid.shadowsocksr.' + sid + '.http_host', params.get("host") ? decodeURIComponent(params.get("host")) : "");
setElementValue('cbid.shadowsocksr.' + sid + '.http_path', params.get("path") ? decodeURIComponent(params.get("path")) : "");
}
var sipIndex = url0.indexOf("@");
var userInfo = url0.substring(0, sipIndex); // 格式uuid:password
var hostPart = url0.substring(sipIndex + 1); // 格式hostname:port
var userInfoSplitIndex = userInfo.indexOf(":");
if(userInfoSplitIndex < 0) {
// 格式错误
s.innerHTML = "<font style='color:red'><%:Userinfo format error.%></font>";
break;
}
s.innerHTML = "<font color='green'><%:Import configuration information successfully.%></font>";
var method = userInfo.substring(0, userInfoSplitIndex);
var password = userInfo.substring(userInfoSplitIndex + 1);
var url = new URL("http://" + hostPart); // 用 URL 提取 host 与 port
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].value = ssu[0];
document.getElementsByName('cbid.shadowsocksr.' + sid + '.type')[0].dispatchEvent(event);
document.getElementsByName('cbid.shadowsocksr.' + sid + '.server')[0].value = url.hostname;
document.getElementsByName('cbid.shadowsocksr.' + sid + '.server_port')[0].value = url.port;
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tuic_uuid')[0].value = method;
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tuic_ip')[0].value = params.ip || "";
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tuic_passwd')[0].value = password;
document.getElementsByName('cbid.shadowsocksr.' + sid + '.udp_relay_mode')[0].value = params.udp_relay_mode || "native";
document.getElementsByName('cbid.shadowsocksr.' + sid + '.congestion_control')[0].value = params.congestion_control || "cubic";
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tuic_alpn')[0].value = params.alpn || "";
document.getElementsByName('cbid.shadowsocksr.' + sid + '.heartbeat')[0].value = params.heartbeat || "3";
document.getElementsByName('cbid.shadowsocksr.' + sid + '.timeout')[0].value = params.timeout || "8";
document.getElementsByName('cbid.shadowsocksr.' + sid + '.gc_interval')[0].value = params.gc_interval || "3";
document.getElementsByName('cbid.shadowsocksr.' + sid + '.gc_lifetime')[0].value = params.gc_lifetime || "15";
document.getElementsByName('cbid.shadowsocksr.' + sid + '.send_window')[0].value = params.send_window || "20971520";
document.getElementsByName('cbid.shadowsocksr.' + sid + '.receive_window')[0].value = params.receive_window || "10485760";
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tuic_max_package_size')[0].value = params.max_packet_size || "1500";
if (params["disable_sni"] === "1" || params["disable_sni"] === "true") {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.disable_sni')[0].checked = true;
document.getElementsByName('cbid.shadowsocksr.' + sid + '.disable_sni')[0].dispatchEvent(event);
}
if (params["zero_rtt_handshake"] === "1" || params["zero_rtt_handshake"] === "true") {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.zero_rtt_handshake')[0].checked = true;
document.getElementsByName('cbid.shadowsocksr.' + sid + '.zero_rtt_handshake')[0].dispatchEvent(event);
}
if (params["dual_stack"] === "1" || params["dual_stack"] === "true") {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tuic_dual_stack')[0].checked = true;
document.getElementsByName('cbid.shadowsocksr.' + sid + '.tuic_dual_stack')[0].dispatchEvent(event);
if (params.ipstack_prefer && params.ipstack_prefer.trim() !== "") {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.ipstack_prefer')[0].value = params.ipstack_prefer;
}
}
if (params["allowInsecure"] === "1" || params["allowInsecure"] === "true") {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.insecure')[0].checked = true;
document.getElementsByName('cbid.shadowsocksr.' + sid + '.insecure')[0].dispatchEvent(event);
}
if (param != undefined) {
document.getElementsByName('cbid.shadowsocksr.' + sid + '.alias')[0].value = decodeURIComponent(param);
}
s.innerHTML = "<font style=\'color:green\'><%:Import configuration information successfully.%></font>";
return false;
default:
s.innerHTML = "<font color='red'><%:Invalid format.%></font>";
s.innerHTML = "<font style=\'color:red\'><%:Invalid format.%></font>";
return false;
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1258,8 +1258,18 @@ start_server() {
[ "$(uci_get_by_name $1 enable 0)" == "0" ] && return 1
let server_count=server_count+1
if [ "$server_count" == "1" ]; then
if ! (iptables-save -t filter | grep SSR-SERVER-RULE >/dev/null); then
iptables -N SSR-SERVER-RULE && iptables -t filter -I INPUT -j SSR-SERVER-RULE
if command -v nft >/dev/null 2>&1; then
# nftables / fw4
if ! nft list chain inet fw4 SSR-SERVER-RULE >/dev/null 2>&1; then
nft add chain inet fw4 SSR-SERVER-RULE
nft insert rule inet fw4 input jump SSR-SERVER-RULE
fi
else
# iptables / fw3
if ! (iptables-save -t filter | grep -q "SSR-SERVER-RULE"); then
iptables -N SSR-SERVER-RULE
iptables -t filter -I INPUT -j SSR-SERVER-RULE
fi
fi
fi
local type=$(uci_get_by_name $1 type)
@@ -1287,17 +1297,15 @@ start_server() {
echolog "Server:Socks5 Server$server_count Started!"
;;
esac
ssr_server_port=$(uci show shadowsocksr | grep 'server_config.*server_port' | awk -F"'" '{print $2}' | tr "\n" " ")
if [ -n "$ssr_server_port" ]; then
uci -q delete firewall.shadowsocksr_server
uci set firewall.shadowsocksr_server=rule
uci set firewall.shadowsocksr_server.name="shadowsocksr_server"
uci set firewall.shadowsocksr_server.target="ACCEPT"
uci set firewall.shadowsocksr_server.src="wan"
uci set firewall.shadowsocksr_server.dest_port="$ssr_server_port"
uci set firewall.shadowsocksr_server.enabled="1"
uci commit firewall
/etc/init.d/firewall reload >/dev/null 2>&1
server_port=$(uci_get_by_name $1 server_port)
if command -v nft >/dev/null 2>&1; then
# nftables / fw4
nft add rule inet fw4 SSR-SERVER-RULE tcp dport $server_port accept
nft add rule inet fw4 SSR-SERVER-RULE udp dport $server_port accept
else
# iptables / fw3
iptables -t filter -A SSR-SERVER-RULE -p tcp --dport $server_port -j ACCEPT
iptables -t filter -A SSR-SERVER-RULE -p udp --dport $server_port -j ACCEPT
fi
return 0
}
@@ -1307,17 +1315,37 @@ start_server() {
if [ ! -f $FWI ]; then
echo '#!/bin/sh' >$FWI
fi
extract_rules() {
echo "*filter"
iptables-save -t filter | grep SSR-SERVER-RULE | sed -e "s/^-A INPUT/-I INPUT/"
echo 'COMMIT'
}
cat <<-EOF >>$FWI
iptables-save -c | grep -v "SSR-SERVER" | iptables-restore -c
iptables-restore -n <<-EOT
$(extract_rules)
EOT
EOF
if command -v nft >/dev/null 2>&1; then
# nftables / fw4
extract_rules() {
nft list chain inet fw4 SSR-SERVER-RULE 2>/dev/null | \
grep -v 'chain SSR-SERVER-RULE' | grep -v '^\s*{' | grep -v '^\s*}' | sed 's/ counter//g'
}
cat <<-EOF >>$FWI
nft flush chain inet fw4 SSR-SERVER-RULE 2>/dev/null || true
nft -f - <<-EOT
table inet fw4 {
chain SSR-SERVER-RULE {
type filter hook input priority 0; policy accept;
$(extract_rules)
}
}
EOT
EOF
else
# iptables / fw3
extract_rules() {
echo "*filter"
iptables-save -t filter | grep SSR-SERVER-RULE | sed -e "s/^-A INPUT/-I INPUT/"
echo 'COMMIT'
}
cat <<-EOF >>$FWI
iptables-save -c | grep -v "SSR-SERVER" | iptables-restore -c
iptables-restore -n <<-EOT
$(extract_rules)
EOT
EOF
fi
}
config_load $NAME
@@ -1455,6 +1483,28 @@ stop() {
unlock
set_lock
/usr/bin/ssr-rules -f
if command -v nft >/dev/null 2>&1; then
# nftables / fw4
#local srulecount=$(nft list ruleset 2>/dev/null | grep -c 'SSR-SERVER-RULE')
#local srulecount=$(nft list chain inet fw4 SSR-SERVER-RULE 2>/dev/null | grep -c 'dport')
local srulecount=$(nft list chain inet fw4 SSR-SERVER-RULE | grep -vE '^\s*(chain|{|})' | wc -l)
else
# iptables / fw3
local srulecount=$(iptables -L | grep SSR-SERVER-RULE | wc -l)
fi
if [ $srulecount -gt 0 ]; then
if command -v nft >/dev/null 2>&1; then
# nftables / fw4
nft flush chain inet fw4 SSR-SERVER-RULE 2>/dev/null || true
nft delete rule inet fw4 input jump SSR-SERVER-RULE 2>/dev/null || true
nft delete chain inet fw4 SSR-SERVER-RULE 2>/dev/null || true
else
# iptables / fw3
iptables -F SSR-SERVER-RULE
iptables -t filter -D INPUT -j SSR-SERVER-RULE
iptables -X SSR-SERVER-RULE 2>/dev/null
fi
fi
if [ -z "$switch_server" ]; then
$PS -w | grep -v "grep" | grep ssr-switch | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1 &
rm -f /var/lock/ssr-switch.lock
@@ -1465,7 +1515,7 @@ stop() {
( \
# Graceful kill first, so programs have the chance to stop its subprocesses
$PS -w | grep -v "grep" | grep "$TMP_PATH" | awk '{print $1}' | xargs kill >/dev/null 2>&1 ; \
sleep 1s; \
sleep 3s; \
# Force kill hanged programs
$PS -w | grep -v "grep" | grep "$TMP_PATH" | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1 ; \
)
@@ -1488,9 +1538,6 @@ stop() {
/etc/init.d/dnsmasq restart >/dev/null 2>&1
fi
uci -q delete firewall.shadowsocksr_server
uci commit firewall
/etc/init.d/firewall reload >/dev/null 2>&1
del_cron
unset_lock
}

View File

@@ -1,37 +1,7 @@
aaplimg.com
account.synology.com
apple-cloudkit.com
apple.co
apple.com
apple.com.cn
appstore.com
bilibili.com
bilibili.cn
bilivideo.com
bilivideo.cn
biliapi.com
biliapi.net
bilibili.cn
bilibili.com
bilivideo.cn
bilivideo.com
bing.com
cdn-apple.com
checkip.dyndns.org
checkip.synology.com
checkipv6.synology.com
checkport.synology.com
crashlytics.com
ddns.synology.com
gitmirror.com
icloud-content.com
icloud.com
icloud.com.cn
images-cn.ssl-images-amazon.com
mirrorlist.centos.org
mzstatic.com
office365.com
officecdn-microsoft-com.akamaized.net
teamviewer.com
whatismyip.akamai.com
windows.com
windowsupdate.com
worldbank.org
worldscientific.com
www-cdn.icloud.com.akadns.net
apple.com

View File

@@ -9,12 +9,12 @@
# Detect firewall version and set appropriate tools
detect_firewall() {
if command -v fw4 >/dev/null 2>&1 && [ -f /usr/share/nftables.d/ruleset-post/99-shadowsocksr.nft ]; then
USE_NFT=1
NFT="nft"
elif command -v nft >/dev/null 2>&1 && [ "$(uci get firewall.@defaults[0].syn_flood 2>/dev/null)" != "" ] && [ ! -f /etc/config/firewall3 ]; then
USE_NFT=1
NFT="nft"
if command -v nft >/dev/null 2>&1 && \
[ -n "$(uci get firewall.@defaults[0].syn_flood 2>/dev/null)" ] && \
! grep -q "fw3" /etc/init.d/firewall 2>/dev/null; then
USE_NFT=1
NFT="nft"
FWI=$(uci get firewall.shadowsocksr.path 2>/dev/null) # firewall include file
else
USE_NFT=0
IPT="iptables -t nat" # alias of iptables
@@ -26,6 +26,7 @@ detect_firewall() {
detect_firewall
TAG="_SS_SPEC_RULE_" # comment tag
usage() {
cat <<-EOF
Usage: ssr-rules [options]
@@ -83,10 +84,14 @@ flush_r() {
flush_nftables() {
# Remove nftables rules and sets
$NFT delete table inet ss_spec 2>/dev/null
$NFT delete table ip ss_spec 2>/dev/null
$NFT delete table ip ss_spec_mangle 2>/dev/null
# Clean up routing rules
ip rule del fwmark 0x01/0x01 table 100 2>/dev/null
ip route del local 0.0.0.0/0 dev lo table 100 2>/dev/null
[ -n "$FWI" ] && echo '#!/bin/sh' >"$FWI"
return 0
}
@@ -128,43 +133,43 @@ ipset_r() {
}
ipset_nft() {
[ -f "$IGNORE_LIST" ] && /usr/share/shadowsocksr/chinaipset.sh $IGNORE_LIST
[ -f "$IGNORE_LIST" ] && /usr/share/shadowsocksr/chinaipset.sh "$IGNORE_LIST"
# Create nftables table and sets
$NFT add table inet ss_spec 2>/dev/null
$NFT add set inet ss_spec ss_spec_wan_ac { type ipv4_addr\; flags interval\; } 2>/dev/null
$NFT add set inet ss_spec gmlan { type ipv4_addr\; flags interval\; } 2>/dev/null
$NFT add set inet ss_spec fplan { type ipv4_addr\; flags interval\; } 2>/dev/null
$NFT add set inet ss_spec bplan { type ipv4_addr\; flags interval\; } 2>/dev/null
$NFT add set inet ss_spec whitelist { type ipv4_addr\; flags interval\; } 2>/dev/null
$NFT add set inet ss_spec blacklist { type ipv4_addr\; flags interval\; } 2>/dev/null
$NFT add set inet ss_spec netflix { type ipv4_addr\; flags interval\; } 2>/dev/null
$NFT add set inet ss_spec ss_spec_wan_ac '{ type ipv4_addr; flags interval; }' 2>/dev/null
$NFT add set inet ss_spec gmlan '{ type ipv4_addr; flags interval; }' 2>/dev/null
$NFT add set inet ss_spec fplan '{ type ipv4_addr; flags interval; }' 2>/dev/null
$NFT add set inet ss_spec bplan '{ type ipv4_addr; flags interval; }' 2>/dev/null
$NFT add set inet ss_spec whitelist '{ type ipv4_addr; flags interval; }' 2>/dev/null
$NFT add set inet ss_spec blacklist '{ type ipv4_addr; flags interval; }' 2>/dev/null
$NFT add set inet ss_spec netflix '{ type ipv4_addr; flags interval; }' 2>/dev/null
# Add IP addresses to sets
for ip in $LAN_GM_IP; do
$NFT add element inet ss_spec gmlan { $ip }
$NFT add element inet ss_spec gmlan "{ $ip }"
done
for ip in $LAN_FP_IP; do
$NFT add element inet ss_spec fplan { $ip }
$NFT add element inet ss_spec fplan "{ $ip }"
done
for ip in $LAN_BP_IP; do
$NFT add element inet ss_spec bplan { $ip }
$NFT add element inet ss_spec bplan "{ $ip }"
done
for ip in $WAN_BP_IP; do
$NFT add element inet ss_spec whitelist { $ip }
$NFT add element inet ss_spec whitelist "{ $ip }"
done
for ip in $WAN_FW_IP; do
$NFT add element inet ss_spec blacklist { $ip }
$NFT add element inet ss_spec blacklist "{ $ip }"
done
# Create main chain for WAN access control
$NFT add chain inet ss_spec ss_spec_wan_ac { type nat hook prerouting priority dstnat\; }
$NFT add chain inet ss_spec ss_spec_wan_ac '{ type nat hook prerouting priority dstnat; }' 2>/dev/null
$NFT add rule inet ss_spec ss_spec_wan_ac tcp dport 53 ip daddr 127.0.0.0/8 return
$NFT add rule inet ss_spec ss_spec_wan_ac tcp dport != 53 ip daddr $server return
$NFT add rule inet ss_spec ss_spec_wan_ac tcp dport != 53 ip daddr "$server" return
# Add special IP ranges to WAN AC set
for ip in $(gen_spec_iplist); do
$NFT add element inet ss_spec ss_spec_wan_ac { $ip }
$NFT add element inet ss_spec ss_spec_wan_ac "{ $ip }"
done
# Set up mode-specific rules
@@ -212,19 +217,19 @@ ipset_nft() {
# Shunt/Netflix rules
if [ "$SHUNT_PORT" != "0" ]; then
for ip in $(cat ${SHUNT_LIST:=/dev/null} 2>/dev/null); do
$NFT add element inet ss_spec netflix { $ip }
for ip in $(cat "${SHUNT_LIST:=/dev/null}" 2>/dev/null); do
$NFT add element inet ss_spec netflix "{ $ip }"
done
case "$SHUNT_PORT" in
1)
$NFT add rule inet ss_spec ss_spec_wan_ac tcp dport $PROXY_PORTS ip daddr @netflix redirect to :$local_port
$NFT add rule inet ss_spec ss_spec_wan_ac tcp dport "$PROXY_PORTS" ip daddr @netflix redirect to :"$local_port"
;;
*)
$NFT add rule inet ss_spec ss_spec_wan_ac tcp dport $PROXY_PORTS ip daddr @netflix redirect to :$SHUNT_PORT
if [ "$SHUNT_PROXY" == "1" ]; then
$NFT add rule inet ss_spec ss_spec_wan_ac tcp dport $PROXY_PORTS ip daddr $SHUNT_IP redirect to :$local_port
$NFT add rule inet ss_spec ss_spec_wan_ac tcp dport "$PROXY_PORTS" ip daddr @netflix redirect to :"$SHUNT_PORT"
if [ "$SHUNT_PROXY" = "1" ]; then
$NFT add rule inet ss_spec ss_spec_wan_ac tcp dport "$PROXY_PORTS" ip daddr "$SHUNT_IP" redirect to :"$local_port"
else
$NFT add element inet ss_spec whitelist { $SHUNT_IP }
$NFT add element inet ss_spec whitelist "{ $SHUNT_IP }"
fi
;;
esac
@@ -233,12 +238,12 @@ ipset_nft() {
}
ipset_iptables() {
[ -f "$IGNORE_LIST" ] && /usr/share/shadowsocksr/chinaipset.sh $IGNORE_LIST
[ -f "$IGNORE_LIST" ] && /usr/share/shadowsocksr/chinaipset.sh "$IGNORE_LIST"
$IPT -N SS_SPEC_WAN_AC 2>/dev/null
$IPT -I SS_SPEC_WAN_AC -p tcp --dport 53 -d 127.0.0.0/8 -j RETURN
$IPT -I SS_SPEC_WAN_AC -p tcp ! --dport 53 -d $server -j RETURN
$IPT -I SS_SPEC_WAN_AC -p tcp ! --dport 53 -d "$server" -j RETURN
ipset -N gmlan hash:net 2>/dev/null
for ip in $LAN_GM_IP; do ipset -! add gmlan $ip; done
for ip in $LAN_GM_IP; do ipset -! add gmlan "$ip"; done
case "$RUNMODE" in
router)
ipset -! -R <<-EOF || return 1
@@ -267,34 +272,34 @@ ipset_iptables() {
;;
esac
ipset -N fplan hash:net 2>/dev/null
for ip in $LAN_FP_IP; do ipset -! add fplan $ip; done
for ip in $LAN_FP_IP; do ipset -! add fplan "$ip"; done
$IPT -I SS_SPEC_WAN_AC -m set --match-set fplan src -j SS_SPEC_WAN_FW
ipset -N bplan hash:net 2>/dev/null
for ip in $LAN_BP_IP; do ipset -! add bplan $ip; done
for ip in $LAN_BP_IP; do ipset -! add bplan "$ip"; done
$IPT -I SS_SPEC_WAN_AC -m set --match-set bplan src -j RETURN
ipset -N whitelist hash:net 2>/dev/null
ipset -N blacklist hash:net 2>/dev/null
$IPT -I SS_SPEC_WAN_AC -m set --match-set blacklist dst -j SS_SPEC_WAN_FW
$IPT -I SS_SPEC_WAN_AC -m set --match-set whitelist dst -j RETURN
if [ $(ipset list music -name -quiet | grep music) ]; then
if ipset list music -name -quiet >/dev/null 2>&1; then
$IPT -I SS_SPEC_WAN_AC -m set --match-set music dst -j RETURN 2>/dev/null
fi
for ip in $WAN_BP_IP; do ipset -! add whitelist $ip; done
for ip in $WAN_FW_IP; do ipset -! add blacklist $ip; done
for ip in $WAN_BP_IP; do ipset -! add whitelist "$ip"; done
for ip in $WAN_FW_IP; do ipset -! add blacklist "$ip"; done
if [ "$SHUNT_PORT" != "0" ]; then
ipset -N netflix hash:net 2>/dev/null
for ip in $(cat ${SHUNT_LIST:=/dev/null} 2>/dev/null); do ipset -! add netflix $ip; done
for ip in $(cat "${SHUNT_LIST:=/dev/null}" 2>/dev/null); do ipset -! add netflix "$ip"; done
case "$SHUNT_PORT" in
0) ;;
1)
$IPT -I SS_SPEC_WAN_AC -p tcp -m set --match-set netflix dst -j REDIRECT --to-ports $local_port
$IPT -I SS_SPEC_WAN_AC -p tcp -m set --match-set netflix dst -j REDIRECT --to-ports "$local_port"
;;
*)
$IPT -I SS_SPEC_WAN_AC -p tcp -m set --match-set netflix dst -j REDIRECT --to-ports $SHUNT_PORT
if [ "$SHUNT_PROXY" == "1" ]; then
$IPT -I SS_SPEC_WAN_AC -p tcp -d $SHUNT_IP -j REDIRECT --to-ports $local_port
$IPT -I SS_SPEC_WAN_AC -p tcp -m set --match-set netflix dst -j REDIRECT --to-ports "$SHUNT_PORT"
if [ "$SHUNT_PROXY" = "1" ]; then
$IPT -I SS_SPEC_WAN_AC -p tcp -d "$SHUNT_IP" -j REDIRECT --to-ports "$local_port"
else
ipset -! add whitelist $SHUNT_IP
ipset -! add whitelist "$SHUNT_IP"
fi
;;
esac
@@ -312,28 +317,38 @@ fw_rule() {
}
fw_rule_nft() {
# Create forward chain for nftables
$NFT add chain inet ss_spec ss_spec_wan_fw
$NFT add rule inet ss_spec ss_spec_wan_fw ip daddr 0.0.0.0/8 return
$NFT add rule inet ss_spec ss_spec_wan_fw ip daddr 10.0.0.0/8 return
$NFT add rule inet ss_spec ss_spec_wan_fw ip daddr 127.0.0.0/8 return
$NFT add rule inet ss_spec ss_spec_wan_fw ip daddr 169.254.0.0/16 return
$NFT add rule inet ss_spec ss_spec_wan_fw ip daddr 172.16.0.0/12 return
$NFT add rule inet ss_spec ss_spec_wan_fw ip daddr 192.168.0.0/16 return
$NFT add rule inet ss_spec ss_spec_wan_fw ip daddr 224.0.0.0/4 return
$NFT add rule inet ss_spec ss_spec_wan_fw ip daddr 240.0.0.0/4 return
# Create forward chain with better error handling
if ! $NFT list chain inet ss_spec ss_spec_wan_fw >/dev/null 2>&1; then
$NFT add chain inet ss_spec ss_spec_wan_fw 2>/dev/null || {
loger 3 "Failed to create forward chain"
return 1
}
fi
# Exclude special local addresses
$NFT add rule inet ss_spec ss_spec_wan_fw ip daddr 0.0.0.0/8 return 2>/dev/null
$NFT add rule inet ss_spec ss_spec_wan_fw ip daddr 10.0.0.0/8 return 2>/dev/null
$NFT add rule inet ss_spec ss_spec_wan_fw ip daddr 127.0.0.0/8 return 2>/dev/null
$NFT add rule inet ss_spec ss_spec_wan_fw ip daddr 169.254.0.0/16 return 2>/dev/null
$NFT add rule inet ss_spec ss_spec_wan_fw ip daddr 172.16.0.0/12 return 2>/dev/null
$NFT add rule inet ss_spec ss_spec_wan_fw ip daddr 192.168.0.0/16 return 2>/dev/null
$NFT add rule inet ss_spec ss_spec_wan_fw ip daddr 224.0.0.0/4 return 2>/dev/null
$NFT add rule inet ss_spec ss_spec_wan_fw ip daddr 240.0.0.0/4 return 2>/dev/null
# redirect/translation: when PROXY_PORTS present, redirect those tcp ports to local_port
if [ -n "$PROXY_PORTS" ]; then
$NFT add rule inet ss_spec ss_spec_wan_fw tcp $PROXY_PORTS redirect to :$local_port 2>/dev/null || {
$NFT add rule inet ss_spec ss_spec_wan_fw tcp dport "$PROXY_PORTS" redirect to :"$local_port" 2>/dev/null || {
loger 3 "Can't redirect, please check nftables."
exit 1
}
else
$NFT add rule inet ss_spec ss_spec_wan_fw tcp dport != 22 redirect to :$local_port 2>/dev/null || {
# default: redirect everything except ssh(22)
$NFT add rule inet ss_spec ss_spec_wan_fw tcp dport != 22 redirect to :"$local_port" 2>/dev/null || {
loger 3 "Can't redirect, please check nftables."
exit 1
}
fi
return 0
}
@@ -347,7 +362,7 @@ fw_rule_iptables() {
$IPT -A SS_SPEC_WAN_FW -d 192.168.0.0/16 -j RETURN
$IPT -A SS_SPEC_WAN_FW -d 224.0.0.0/4 -j RETURN
$IPT -A SS_SPEC_WAN_FW -d 240.0.0.0/4 -j RETURN
$IPT -A SS_SPEC_WAN_FW -p tcp $PROXY_PORTS -j REDIRECT --to-ports $local_port 2>/dev/null || {
$IPT -A SS_SPEC_WAN_FW -p tcp $PROXY_PORTS -j REDIRECT --to-ports "$local_port" 2>/dev/null || {
loger 3 "Can't redirect, please check the iptables."
exit 1
}
@@ -366,13 +381,13 @@ ac_rule() {
ac_rule_nft() {
local MATCH_SET_CONDITION=""
if [ -n "$LAN_AC_IP" ]; then
# Create LAN access control set
$NFT add set inet ss_spec ss_spec_lan_ac { type ipv4_addr\; flags interval\; } 2>/dev/null
for ip in ${LAN_AC_IP:1}; do
$NFT add element inet ss_spec ss_spec_lan_ac { $ip }
# Create LAN access control set if needed
$NFT add set inet ss_spec ss_spec_lan_ac '{ type ipv4_addr; flags interval; }' 2>/dev/null
for ip in ${LAN_AC_IP#?}; do
[ -n "$ip" ] && $NFT add element inet ss_spec ss_spec_lan_ac "{ $ip }" 2>/dev/null
done
case "${LAN_AC_IP:0:1}" in
case "${LAN_AC_IP%${LAN_AC_IP#?}}" in
w | W)
MATCH_SET_CONDITION="ip saddr @ss_spec_lan_ac"
;;
@@ -385,23 +400,24 @@ ac_rule_nft() {
;;
esac
fi
# Create prerouting rules
# Build a rule in the prerouting hook chain that jumps to business chain with conditions
if [ -z "$Interface" ]; then
# generic prerouting jump already exists (see ipset_nft), but if we have MATCH_SET_CONDITION we add a more specific rule
if [ -n "$MATCH_SET_CONDITION" ]; then
$NFT add rule inet ss_spec ss_spec_wan_ac tcp dport $EXT_ARGS $MATCH_SET_CONDITION comment "\"$TAG\"" goto ss_spec_wan_ac
else
$NFT add rule inet ss_spec ss_spec_wan_ac tcp dport $EXT_ARGS comment "\"$TAG\"" goto ss_spec_wan_ac
# add a more specific rule at the top of ss_spec_prerouting
$NFT insert rule inet ss_spec ss_spec_prerouting tcp $MATCH_SET_CONDITION comment "\"$TAG\"" jump ss_spec_wan_ac 2>/dev/null
fi
else
# For each Interface, find its actual ifname and add an iifname-limited prerouting rule
for name in $Interface; do
local IFNAME=$(uci -P /var/state get network.$name.ifname 2>/dev/null)
[ -z "$IFNAME" ] && IFNAME=$(uci -P /var/state get network.$name.device 2>/dev/null)
local IFNAME=$(uci -P /var/state get network."$name".ifname 2>/dev/null)
[ -z "$IFNAME" ] && IFNAME=$(uci -P /var/state get network."$name".device 2>/dev/null)
if [ -n "$IFNAME" ]; then
if [ -n "$MATCH_SET_CONDITION" ]; then
$NFT add rule inet ss_spec ss_spec_wan_ac iifname $IFNAME tcp dport $EXT_ARGS $MATCH_SET_CONDITION comment "\"$TAG\"" goto ss_spec_wan_ac
$NFT insert rule inet ss_spec ss_spec_prerouting iifname "$IFNAME" tcp $MATCH_SET_CONDITION comment "\"$TAG\"" jump ss_spec_wan_ac 2>/dev/null
else
$NFT add rule inet ss_spec ss_spec_wan_ac iifname $IFNAME tcp dport $EXT_ARGS comment "\"$TAG\"" goto ss_spec_wan_ac
$NFT insert rule inet ss_spec ss_spec_prerouting iifname "$IFNAME" tcp comment "\"$TAG\"" jump ss_spec_wan_ac 2>/dev/null
fi
fi
done
@@ -409,21 +425,21 @@ ac_rule_nft() {
case "$OUTPUT" in
1)
# Add output rules
$NFT add chain inet ss_spec ss_spec_output { type nat hook output priority dstnat\; }
$NFT add rule inet ss_spec ss_spec_output tcp dport $EXT_ARGS comment "\"$TAG\"" goto ss_spec_wan_ac
# create output hook chain & route output traffic into router chain
$NFT add chain inet ss_spec ss_spec_output '{ type nat hook output priority dstnat; }' 2>/dev/null
$NFT add rule inet ss_spec ss_spec_output tcp comment "\"$TAG\"" jump ss_spec_wan_ac 2>/dev/null
;;
2)
# Router mode output rules
$NFT add set inet ss_spec ssr_gen_router { type ipv4_addr\; flags interval\; } 2>/dev/null
# router mode output chain: create ssr_gen_router set & router chain
$NFT add set inet ss_spec ssr_gen_router '{ type ipv4_addr; flags interval; }' 2>/dev/null
for ip in $(gen_spec_iplist); do
$NFT add element inet ss_spec ssr_gen_router { $ip }
[ -n "$ip" ] && $NFT add element inet ss_spec ssr_gen_router "{ $ip }" 2>/dev/null
done
$NFT add chain inet ss_spec ss_spec_router
$NFT add rule inet ss_spec ss_spec_router ip daddr @ssr_gen_router return
$NFT add rule inet ss_spec ss_spec_router goto ss_spec_wan_fw
$NFT add chain inet ss_spec ss_spec_output { type nat hook output priority dstnat\; }
$NFT add rule inet ss_spec ss_spec_output tcp dport comment "\"$TAG\"" goto ss_spec_router
$NFT add chain inet ss_spec ss_spec_router 2>/dev/null
$NFT add rule inet ss_spec ss_spec_router ip daddr @ssr_gen_router return 2>/dev/null
$NFT add rule inet ss_spec ss_spec_router jump ss_spec_wan_fw 2>/dev/null
$NFT add chain inet ss_spec ss_spec_output '{ type nat hook output priority dstnat; }' 2>/dev/null
$NFT add rule inet ss_spec ss_spec_output tcp comment "\"$TAG\"" jump ss_spec_router 2>/dev/null
;;
esac
return 0
@@ -431,7 +447,7 @@ ac_rule_nft() {
ac_rule_iptables() {
if [ -n "$LAN_AC_IP" ]; then
case "${LAN_AC_IP:0:1}" in
case "${LAN_AC_IP%${LAN_AC_IP#?}}" in
w | W)
MATCH_SET="-m set --match-set ss_spec_lan_ac src"
;;
@@ -446,14 +462,14 @@ ac_rule_iptables() {
fi
ipset -! -R <<-EOF || return 1
create ss_spec_lan_ac hash:net
$(for ip in ${LAN_AC_IP:1}; do echo "add ss_spec_lan_ac $ip"; done)
$(for ip in ${LAN_AC_IP#?}; do echo "add ss_spec_lan_ac $ip"; done)
EOF
if [ -z "$Interface" ]; then
$IPT -I PREROUTING 1 -p tcp $EXT_ARGS $MATCH_SET -m comment --comment "$TAG" -j SS_SPEC_WAN_AC
else
for name in $Interface; do
local IFNAME=$(uci -P /var/state get network.$name.ifname 2>/dev/null)
[ -z "$IFNAME" ] && IFNAME=$(uci -P /var/state get network.$name.device 2>/dev/null)
local IFNAME=$(uci -P /var/state get network."$name".ifname 2>/dev/null)
[ -z "$IFNAME" ] && IFNAME=$(uci -P /var/state get network."$name".device 2>/dev/null)
[ -n "$IFNAME" ] && $IPT -I PREROUTING 1 ${IFNAME:+-i $IFNAME} -p tcp $EXT_ARGS $MATCH_SET -m comment --comment "$TAG" -j SS_SPEC_WAN_AC
done
fi
@@ -487,75 +503,85 @@ tp_rule() {
}
tp_rule_nft() {
# Set up routing for TPROXY
ip rule add fwmark 0x01/0x01 table 100
ip route add local 0.0.0.0/0 dev lo table 100
# Create TPROXY chain in mangle table
[ -n "$TPROXY" ] || return 0
# set up routing table for tproxy
ip rule add fwmark 0x01/0x01 table 100 2>/dev/null
ip route add local 0.0.0.0/0 dev lo table 100 2>/dev/null
# create mangle table and tproxy chain
$NFT add table ip ss_spec_mangle 2>/dev/null
$NFT add chain ip ss_spec_mangle ss_spec_tproxy { type filter hook prerouting priority mangle\; }
# Add basic return rules
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp dport 53 return
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp ip daddr 0.0.0.0/8 return
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp ip daddr 10.0.0.0/8 return
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp ip daddr 127.0.0.0/8 return
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp ip daddr 169.254.0.0/16 return
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp ip daddr 172.16.0.0/12 return
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp ip daddr 192.168.0.0/16 return
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp ip daddr 224.0.0.0/4 return
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp ip daddr 240.0.0.0/4 return
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp dport != 53 ip daddr $SERVER return
# Handle different UDP server
# use priority mangle for compatibility with other rules
$NFT add chain ip ss_spec_mangle ss_spec_tproxy '{ type filter hook prerouting priority mangle; }' 2>/dev/null
# basic return rules in tproxy chain
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp dport 53 return 2>/dev/null
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp ip daddr 0.0.0.0/8 return 2>/dev/null
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp ip daddr 10.0.0.0/8 return 2>/dev/null
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp ip daddr 127.0.0.0/8 return 2>/dev/null
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp ip daddr 169.254.0.0/16 return 2>/dev/null
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp ip daddr 172.16.0.0/12 return 2>/dev/null
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp ip daddr 192.168.0.0/16 return 2>/dev/null
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp ip daddr 224.0.0.0/4 return 2>/dev/null
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp ip daddr 240.0.0.0/4 return 2>/dev/null
# avoid redirecting to udp server address - 修正变量名
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp dport != 53 ip daddr "$server" return 2>/dev/null
# if server != SERVER add SERVER to whitelist set (so tproxy won't touch it)
if [ "$server" != "$SERVER" ]; then
$NFT add element inet ss_spec whitelist { $SERVER }
$NFT add element inet ss_spec whitelist "{ $SERVER }" 2>/dev/null
fi
# Access control rules
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp ip saddr @bplan return
# access control and tproxy rules
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp ip saddr @bplan return 2>/dev/null
if [ -n "$PROXY_PORTS" ]; then
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp $PROXY_PORTS ip saddr @fplan tproxy to :$LOCAL_PORT meta mark set 0x01
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp dport "$PROXY_PORTS" ip saddr @fplan tproxy to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
else
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp ip saddr @fplan tproxy to :$LOCAL_PORT meta mark set 0x01
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp ip saddr @fplan tproxy to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
fi
# Handle different run modes for nftables
case "$RUNMODE" in
router)
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp ip daddr @ss_spec_wan_ac return
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp ip daddr @china return
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp dport 80 drop
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp ip saddr @gmlan ip daddr != @china tproxy to :$LOCAL_PORT meta mark set 0x01
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp ip daddr @ss_spec_wan_ac return 2>/dev/null
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp ip daddr @china return 2>/dev/null
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp dport 80 drop 2>/dev/null
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp ip saddr @gmlan ip daddr != @china tproxy to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
if [ -n "$PROXY_PORTS" ]; then
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp $PROXY_PORTS ip daddr != @ss_spec_wan_ac tproxy to :$LOCAL_PORT meta mark set 0x01
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp dport "$PROXY_PORTS" ip daddr != @ss_spec_wan_ac tproxy to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
else
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp ip daddr != @ss_spec_wan_ac tproxy to :$LOCAL_PORT meta mark set 0x01
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp ip daddr != @ss_spec_wan_ac tproxy to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
fi
;;
gfw)
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp ip daddr @china return
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp dport 80 drop
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp ip daddr @china return 2>/dev/null
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp dport 80 drop 2>/dev/null
if [ -n "$PROXY_PORTS" ]; then
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp $PROXY_PORTS ip daddr @gfwlist tproxy to :$LOCAL_PORT meta mark set 0x01
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp dport "$PROXY_PORTS" ip daddr @gfwlist tproxy to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
fi
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp ip saddr @gmlan ip daddr != @china tproxy to :$LOCAL_PORT meta mark set 0x01
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp ip saddr @gmlan ip daddr != @china tproxy to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
;;
oversea)
if [ -n "$PROXY_PORTS" ]; then
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp $PROXY_PORTS ip saddr @oversea tproxy to :$LOCAL_PORT meta mark set 0x01
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp $PROXY_PORTS ip daddr @china tproxy to :$LOCAL_PORT meta mark set 0x01
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp dport "$PROXY_PORTS" ip saddr @oversea tproxy to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp dport "$PROXY_PORTS" ip daddr @china tproxy to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
fi
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp ip saddr @gmlan tproxy to :$LOCAL_PORT meta mark set 0x01
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp ip saddr @gmlan tproxy to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
;;
all)
if [ -n "$PROXY_PORTS" ]; then
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp $PROXY_PORTS tproxy to :$LOCAL_PORT meta mark set 0x01
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp dport "$PROXY_PORTS" tproxy to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
else
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp tproxy to :$LOCAL_PORT meta mark set 0x01
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp tproxy to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
fi
;;
esac
# insert jump from ip prerouting to our tproxy chain
$NFT add rule ip ss_spec_mangle prerouting udp comment "\"$TAG\"" jump ss_spec_tproxy 2>/dev/null
return 0
}
tp_rule_iptables() {
@@ -572,8 +598,8 @@ tp_rule_iptables() {
$ipt -A SS_SPEC_TPROXY -p udp -d 192.168.0.0/16 -j RETURN
$ipt -A SS_SPEC_TPROXY -p udp -d 224.0.0.0/4 -j RETURN
$ipt -A SS_SPEC_TPROXY -p udp -d 240.0.0.0/4 -j RETURN
$ipt -A SS_SPEC_TPROXY -p udp ! --dport 53 -d $SERVER -j RETURN
[ "$server" != "$SERVER" ] && ipset -! add whitelist $SERVER
$ipt -A SS_SPEC_TPROXY -p udp ! --dport 53 -d "$SERVER" -j RETURN
[ "$server" != "$SERVER" ] && ipset -! add whitelist "$SERVER"
$ipt -A SS_SPEC_TPROXY -p udp -m set --match-set bplan src -j RETURN
$ipt -A SS_SPEC_TPROXY -p udp $PROXY_PORTS -m set --match-set fplan src -j TPROXY --on-port "$LOCAL_PORT" --tproxy-mark 0x01/0x01
case "$RUNMODE" in
@@ -603,8 +629,8 @@ tp_rule_iptables() {
$ipt -I PREROUTING 1 -p udp $EXT_ARGS $MATCH_SET -m comment --comment "$TAG" -j SS_SPEC_TPROXY
else
for name in $Interface; do
local IFNAME=$(uci -P /var/state get network.$name.ifname 2>/dev/null)
[ -z "$IFNAME" ] && IFNAME=$(uci -P /var/state get network.$name.device 2>/dev/null)
local IFNAME=$(uci -P /var/state get network."$name".ifname 2>/dev/null)
[ -z "$IFNAME" ] && IFNAME=$(uci -P /var/state get network."$name".device 2>/dev/null)
[ -n "$IFNAME" ] && $ipt -I PREROUTING 1 ${IFNAME:+-i $IFNAME} -p udp $EXT_ARGS $MATCH_SET -m comment --comment "$TAG" -j SS_SPEC_TPROXY
done
fi
@@ -653,15 +679,17 @@ gen_include() {
gen_include_nft() {
# Generate nftables include file for firewall4
cat <<-EOF >>$FWI
[ -n "$FWI" ] && echo '#!/bin/sh' >"$FWI"
cat <<-'EOF' >>"$FWI"
# Clear existing ss_spec tables
nft delete table inet ss_spec 2>/dev/null
nft delete table ip ss_spec 2>/dev/null
nft delete table ip ss_spec_mangle 2>/dev/null
# Restore shadowsocks nftables rules
$(nft list ruleset | grep -A 1000 "table inet ss_spec\|table ip ss_spec")
nft list ruleset | awk '/table (inet|ip) ss_spec/{flag=1} flag'
EOF
chmod +x "$FWI"
}
gen_include_iptables() {
@@ -709,7 +737,7 @@ while getopts ":m:s:l:S:L:i:e:a:B:b:w:p:G:D:F:N:M:I:oOuUfgrczh" arg; do
LAN_BP_IP=$OPTARG
;;
b)
WAN_BP_IP=$(for ip in $OPTARG; do echo $ip; done)
WAN_BP_IP=$(for ip in $OPTARG; do echo "$ip"; done)
;;
w)
WAN_FW_IP=$OPTARG
@@ -767,10 +795,15 @@ while getopts ":m:s:l:S:L:i:e:a:B:b:w:p:G:D:F:N:M:I:oOuUfgrczh" arg; do
esac
done
if [ -z "$server" -o -z "$local_port" ]; then
if [ -z "$server" ] || [ -z "$local_port" ]; then
usage 2
fi
if ! echo "$local_port" | grep -qE '^[0-9]+$'; then
loger 3 "Invalid local port: $local_port"
exit 1
fi
case "$TPROXY" in
1)
SERVER=$server
@@ -782,7 +815,10 @@ case "$TPROXY" in
;;
esac
flush_r && fw_rule && ipset_r && ac_rule && tp_rule && gen_include
RET=$?
[ "$RET" = 0 ] || loger 3 "Start failed!"
exit $RET
if flush_r && fw_rule && ipset_r && ac_rule && tp_rule && gen_include; then
loger 5 "Rules applied successfully"
exit 0
else
loger 3 "Start failed!"
exit 1
fi

View File

@@ -331,8 +331,8 @@ end
mux = (server.v2ray_protocol ~= "wireguard") and {
-- mux
enabled = (server.mux == "1"), -- Mux
concurrency = (server.mux == "1" and (tonumber(server.concurrency) or -1)) or nil, -- TCP 最大并发
xudpConcurrency = (server.mux == "1" and (tonumber(server.xudpConcurrency) or 16)) or nil, -- UDP 最大并发
concurrency = (server.mux == "1" and (tonumber(server.concurrency) or -1)) or nil, -- TCP 最大并发连接数
xudpConcurrency = (server.mux == "1" and (tonumber(server.xudpConcurrency) or 16)) or nil, -- UDP 最大并发连接数
xudpProxyUDP443 = (server.mux == "1" and (server.xudpProxyUDP443 or "reject")) or nil -- 对被代理的 UDP/443 流量处理方式
} or nil
}
@@ -631,6 +631,8 @@ local tuic = {
return nil
end
end)() or nil,
ipstack_prefer = (server.tuic_dual_stack == "1") and server.ipstack_prefer or nil,
skip_cert_verify = (server.insecure == "1" or server.insecure == true or server.insecure == "true"),
disable_sni = (server.disable_sni == "1") and true or false,
zero_rtt_handshake = (server.zero_rtt_handshake == "1") and true or false,
send_window = tonumber(server.send_window),
@@ -715,3 +717,4 @@ function config:handleIndex(index)
end
local f = config:new()
f:handleIndex(server.type)

View File

@@ -27,7 +27,7 @@ config server_subscribe
option auto_update_day_time '2'
option auto_update_min_time '0'
option user_agent 'v2rayN/9.99'
option filter_words '过期时间/剩余流量/QQ群/官网/防失联地址/回国'
option filter_words '过期/套餐/剩余/QQ群/官网/防失联/回国'
config access_control
option lan_ac_mode '0'

File diff suppressed because it is too large Load Diff