🔥 Sync 2026-10-07 17:07:39

This commit is contained in:
github-actions[bot] committed 2026-10-07 17:07:39 +08:00
1 parent 007d8fe1b0
commit ecb79884af
23 files changed
+1659 -78

No files matched your search

+1 -1
View File
@@ -7,7 +7,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-honk PKG_NAME:=luci-app-honk
PKG_VERSION:=1.1.4 PKG_VERSION:=1.1.4
PKG_RELEASE:=15 PKG_RELEASE:=16
LUCI_TITLE:=LuCI support for honk LUCI_TITLE:=LuCI support for honk
LUCI_PKGARCH:=all LUCI_PKGARCH:=all
+59 -22
View File
@@ -91,9 +91,8 @@ function strip_dae_comments(content) {
} }
function parse_native_api(clean_content) { function parse_native_api(clean_content) {
let api_m = match(clean_content, /native_api\s*\{([^}]+)\}/); let block = extract_bracket_block(clean_content, /native_api\s*\{/);
if (!api_m) return null; if (!block) return null;
let block = api_m[1];
let listen_m = match(block, /listen\s*:\s*['"]?([^'" \t\r\n]+)['"]?/); let listen_m = match(block, /listen\s*:\s*['"]?([^'" \t\r\n]+)['"]?/);
let ui_m = match(block, /ui\s*:\s*['"]?([^'" \t\r\n]+)['"]?/); let ui_m = match(block, /ui\s*:\s*['"]?([^'" \t\r\n]+)['"]?/);
@@ -167,36 +166,71 @@ function get_api_config() {
}; };
} }
function remove_bracket_block(content, header_regex) { function find_bracket_block(content, header_regex) {
let lines = split(content, "\n"); let lines = split(content, "\n");
let new_lines = []; let start_idx = -1;
let end_idx = -1;
let in_block = false; let in_block = false;
let depth = 0; let depth = 0;
let in_single = false;
let in_double = false;
for (let idx, line in lines) { for (let idx, line in lines) {
let trimmed = trim(line); let trimmed = trim(line);
if (!in_block && match(trimmed, header_regex)) { if (!in_block && match(trimmed, header_regex)) {
in_block = true; in_block = true;
let open_cnt = length(split(trimmed, "{")) - 1; start_idx = idx;
let close_cnt = length(split(trimmed, "}")) - 1;
depth = open_cnt - close_cnt;
if (open_cnt > 0 && depth <= 0) {
in_block = false;
}
continue;
} }
if (in_block) { if (in_block) {
let open_cnt = length(split(trimmed, "{")) - 1; let len = length(line);
let close_cnt = length(split(trimmed, "}")) - 1; for (let i = 0; i < len; i++) {
depth += (open_cnt - close_cnt); let c = substr(line, i, 1);
if (depth <= 0) { let next_c = (i + 1 < len) ? substr(line, i + 1, 1) : "";
in_block = false; if (c == "'" && !in_double) {
in_single = !in_single;
} else if (c == '"' && !in_single) {
in_double = !in_double;
} else if (!in_single && !in_double) {
if (c == '#' || (c == '/' && next_c == '/')) {
break;
} else if (c == '{') {
depth++;
} else if (c == '}') {
depth--;
if (depth <= 0) {
end_idx = idx;
break;
}
}
}
} }
continue; if (end_idx != -1) break;
} }
}
return { start: start_idx, end: end_idx };
}
push(new_lines, line); function extract_bracket_block(content, header_regex) {
let bounds = find_bracket_block(content, header_regex);
if (bounds.start == -1 || bounds.end == -1) return null;
let lines = split(content, "\n");
let block_lines = [];
for (let i = bounds.start; i <= bounds.end; i++) {
push(block_lines, lines[i]);
}
return join("\n", block_lines);
}
function remove_bracket_block(content, header_regex) {
let bounds = find_bracket_block(content, header_regex);
if (bounds.start == -1 || bounds.end == -1) return content;
let lines = split(content, "\n");
let new_lines = [];
for (let i = 0; i < length(lines); i++) {
if (i < bounds.start || i > bounds.end) {
push(new_lines, lines[i]);
}
} }
return join("\n", new_lines); return join("\n", new_lines);
} }
@@ -221,9 +255,12 @@ function clean_legacy_api_from_config(config_file) {
if (!main_content) return; if (!main_content) return;
let cleaned_main = remove_bracket_block(main_content, /^[#\/]*\s*clash_api\s*\{/); let cleaned_main = remove_bracket_block(main_content, /^[#\/]*\s*clash_api\s*\{/);
cleaned_main = remove_bracket_block(cleaned_main, /^[#\/]*\s*native_api\s*\{/); cleaned_main = remove_bracket_block(cleaned_main, /^[#\/]*\s*native_api\s*\{/);
let exp_m = match(cleaned_main, /experimental\s*\{([^}]+)\}/);
if (exp_m) { let exp_block = extract_bracket_block(cleaned_main, /experimental\s*\{/);
let inner_clean = strip_dae_comments(exp_m[1]); if (exp_block) {
let inner_clean = strip_dae_comments(exp_block);
inner_clean = replace(inner_clean, /experimental\s*\{/, "");
inner_clean = replace(inner_clean, /\}[ \t\r\n]*$/, "");
if (!match(inner_clean, /\S/)) { if (!match(inner_clean, /\S/)) {
cleaned_main = remove_bracket_block(cleaned_main, /^[#\/]*\s*experimental\s*\{/); cleaned_main = remove_bracket_block(cleaned_main, /^[#\/]*\s*experimental\s*\{/);
} }
+2 -2
View File
@@ -1,8 +1,8 @@
include $(TOPDIR)/rules.mk include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-open-box PKG_NAME:=luci-app-open-box
PKG_VERSION:=0.1.292 PKG_VERSION:=0.1.293
PKG_RELEASE:=15 PKG_RELEASE:=16
PKG_MAINTAINER:=liandu2024 PKG_MAINTAINER:=liandu2024
PKG_LICENSE:=GPL-3.0-or-later PKG_LICENSE:=GPL-3.0-or-later
+1 -1
View File
@@ -4,7 +4,7 @@ LUCI_TITLE:=luci-app-ssr-plus
LUCI_PKGARCH:=all LUCI_PKGARCH:=all
PKG_NAME:=luci-app-ssr-plus PKG_NAME:=luci-app-ssr-plus
PKG_VERSION:=196 PKG_VERSION:=196
PKG_RELEASE:=84 PKG_RELEASE:=85
PKG_CONFIG_DEPENDS:= \ PKG_CONFIG_DEPENDS:= \
CONFIG_PACKAGE_$(PKG_NAME)_Iptables_Transparent_Proxy \ CONFIG_PACKAGE_$(PKG_NAME)_Iptables_Transparent_Proxy \
@@ -136,6 +136,9 @@ o:value("https://ispip.clang.cn/all_cn_cidr.txt", translate("Clang.CN.CIDR"))
o:value("https://fastly.jsdelivr.net/gh/gaoyifan/china-operator-ip@ip-lists/china.txt", translate("china-operator-ip")) o:value("https://fastly.jsdelivr.net/gh/gaoyifan/china-operator-ip@ip-lists/china.txt", translate("china-operator-ip"))
o.default = "https://ispip.clang.cn/all_cn.txt" o.default = "https://ispip.clang.cn/all_cn.txt"
o = s:option(Value, "chnroute6_url", translate("China IPv6 list update URL"))
o.default = "https://raw.githubusercontent.com/1715173329/IPCIDR-CHINA/master/ipv6.txt"
o = s:option(Flag, "apple_optimization", translate("Apple domains optimization"), translate("For Apple domains equipped with Chinese mainland CDN, always responsive to Chinese CDN IP addresses")) o = s:option(Flag, "apple_optimization", translate("Apple domains optimization"), translate("For Apple domains equipped with Chinese mainland CDN, always responsive to Chinese CDN IP addresses"))
o.rmempty = false o.rmempty = false
o.default = "1" o.default = "1"
@@ -108,6 +108,17 @@ o:value("router", translate("IP Route Mode"))
o:value("all", translate("Global Mode")) o:value("all", translate("Global Mode"))
o.default = gfw o.default = gfw
o = s:option(Flag, "ipv6_support", translate("IPv6 Traffic Proxy"))
o.description = translate("Proxy IPv6 traffic using the selected running mode (requires nftables). Allow AAAA replies while enabled and restore the DNS filter when stopped.")
o.default = "0"
o.rmempty = false
function o.validate(self, value, section)
if value == "1" and not is_finded("nft") then
return nil, translate("IPv6 proxy requires nftables.")
end
return value
end
o = s:option(ListValue, "dports", translate("Proxy Ports")) o = s:option(ListValue, "dports", translate("Proxy Ports"))
o:value("1", translate("All Ports")) o:value("1", translate("All Ports"))
o:value("2", translate("Only Common Ports")) o:value("2", translate("Only Common Ports"))
@@ -161,12 +172,16 @@ o.description = translate("Custom DNS Server format as tcp://IP:PORT or tls://DO
o.default = "tcp://8.8.4.4:53,tcp://8.8.8.8:53" o.default = "tcp://8.8.4.4:53,tcp://8.8.8.8:53"
o = s:option(Flag, "filter_aaaa", translate("Disable IPv6 for Overseas FQDN")) o = s:option(Flag, "filter_aaaa", translate("Disable IPv6 for Overseas FQDN"))
o:depends("pdnsd_enable", "1") o:depends({pdnsd_enable = "1", ipv6_support = false})
o:depends("pdnsd_enable", "4") o:depends({pdnsd_enable = "4", ipv6_support = false})
o:depends("pdnsd_enable", "7") o:depends({pdnsd_enable = "7", ipv6_support = false})
o.rmempty = false o.rmempty = false
o.default = "1" o.default = "1"
-- Dependency hiding must not delete the saved IPv4-mode preference.
function o.remove(self, section)
end
if is_finded("chinadns-ng") then if is_finded("chinadns-ng") then
o = s:option(Value, "chinadns_ng_tunnel_forward", translate("Anti-pollution DNS Server")) o = s:option(Value, "chinadns_ng_tunnel_forward", translate("Anti-pollution DNS Server"))
o:value("8.8.4.4:53", translate("Google Public DNS (8.8.4.4)")) o:value("8.8.4.4:53", translate("Google Public DNS (8.8.4.4)"))
@@ -21,10 +21,10 @@ o.description = translate("Listen only on the given interface or, if unspecified
s:tab("wan_ac", translate("WAN IP AC")) s:tab("wan_ac", translate("WAN IP AC"))
o = s:taboption("wan_ac", DynamicList, "wan_bp_ips", translate("WAN White List IP")) o = s:taboption("wan_ac", DynamicList, "wan_bp_ips", translate("WAN White List IP"))
o.datatype = "ip4addr" o.datatype = "ipaddr"
o = s:taboption("wan_ac", DynamicList, "wan_fw_ips", translate("WAN Force Proxy IP")) o = s:taboption("wan_ac", DynamicList, "wan_fw_ips", translate("WAN Force Proxy IP"))
o.datatype = "ip4addr" o.datatype = "ipaddr"
-- Part of LAN -- Part of LAN
s:tab("lan_ac", translate("LAN IP AC")) s:tab("lan_ac", translate("LAN IP AC"))
@@ -37,7 +37,7 @@ o.rmempty = false
o = s:taboption("lan_ac", DynamicList, "lan_ac_ips", translate("LAN Host List")) o = s:taboption("lan_ac", DynamicList, "lan_ac_ips", translate("LAN Host List"))
o.datatype = "ipaddr" o.datatype = "ipaddr"
luci.ip.neighbors({family = 4}, function(entry) luci.ip.neighbors({}, function(entry)
if entry.reachable then if entry.reachable then
o:value(entry.dest:string()) o:value(entry.dest:string())
end end
@@ -47,7 +47,7 @@ o:depends("lan_ac_mode", "b")
o = s:taboption("lan_ac", DynamicList, "lan_bp_ips", translate("LAN Bypassed Host List")) o = s:taboption("lan_ac", DynamicList, "lan_bp_ips", translate("LAN Bypassed Host List"))
o.datatype = "ipaddr" o.datatype = "ipaddr"
luci.ip.neighbors({family = 4}, function(entry) luci.ip.neighbors({}, function(entry)
if entry.reachable then if entry.reachable then
o:value(entry.dest:string()) o:value(entry.dest:string())
end end
@@ -55,7 +55,7 @@ end)
o = s:taboption("lan_ac", DynamicList, "lan_fp_ips", translate("LAN Force Proxy Host List")) o = s:taboption("lan_ac", DynamicList, "lan_fp_ips", translate("LAN Force Proxy Host List"))
o.datatype = "ipaddr" o.datatype = "ipaddr"
luci.ip.neighbors({family = 4}, function(entry) luci.ip.neighbors({}, function(entry)
if entry.reachable then if entry.reachable then
o:value(entry.dest:string()) o:value(entry.dest:string())
end end
@@ -63,7 +63,7 @@ end)
o = s:taboption("lan_ac", DynamicList, "lan_gm_ips", translate("Game Mode Host List")) o = s:taboption("lan_ac", DynamicList, "lan_gm_ips", translate("Game Mode Host List"))
o.datatype = "ipaddr" o.datatype = "ipaddr"
luci.ip.neighbors({family = 4}, function(entry) luci.ip.neighbors({}, function(entry)
if entry.reachable then if entry.reachable then
o:value(entry.dest:string()) o:value(entry.dest:string())
end end
@@ -266,6 +266,11 @@ s.rawhtml = true
s.template = "shadowsocksr/refresh" s.template = "shadowsocksr/refresh"
s.value = ip_count .. " " .. translate("Records") s.value = ip_count .. " " .. translate("Records")
s = m:field(DummyValue, "ip6_data", translate("China IPv6 Data"))
s.rawhtml = true
s.template = "shadowsocksr/refresh"
s.value = (tonumber(luci.sys.exec("wc -l < /etc/ssrplus/china6_ssr.txt 2>/dev/null")) or 0) .. " " .. translate("Records")
if uci:get_first("shadowsocksr", 'global', 'apple_optimization', '0') ~= '0' then if uci:get_first("shadowsocksr", 'global', 'apple_optimization', '0') ~= '0' then
s = m:field(DummyValue, "apple_data", translate("Apple Domains Data")) s = m:field(DummyValue, "apple_data", translate("Apple Domains Data"))
s.rawhtml = true s.rawhtml = true
+15
View File
@@ -3870,3 +3870,18 @@ msgstr "空闲会话超时(秒)"
msgid "Minimum idle sessions" msgid "Minimum idle sessions"
msgstr "最小空闲会话数" msgstr "最小空闲会话数"
msgid "IPv6 Traffic Proxy"
msgstr "IPv6 流量代理"
msgid "Proxy IPv6 traffic using the selected running mode (requires nftables). Allow AAAA replies while enabled and restore the DNS filter when stopped."
msgstr "按照所选运行模式代理 IPv6 流量(需要 nftables)。启用时允许 AAAA 解析,停止后恢复原有 DNS 过滤设置。"
msgid "IPv6 proxy requires nftables."
msgstr "IPv6 代理需要 nftables。"
msgid "China IPv6 list update URL"
msgstr "国内 IPv6 地址库更新地址"
msgid "China IPv6 Data"
msgstr "国内 IPv6 地址库"
+68 -12
View File
@@ -127,7 +127,39 @@ uci_get_by_cfgid() {
echo "${ret:=$3}" echo "${ret:=$3}"
} }
ipv6_enabled() {
[ "$(uci_get_by_type global ipv6_support 0)" = "1" ] && [ "$USE_TABLES" = "nftables" ]
}
ipt2socks_listen_args() {
if ipv6_enabled; then
echo "-b 0.0.0.0 -B ::"
else
echo "-b 0.0.0.0 -4"
fi
}
# Restore the user's global AAAA filter when IPv6 proxying is stopped.
set_ipv6_dns_filter() {
local saved
saved="$(uci -q get 'dhcp.@dnsmasq[0]._ssrp_filter_aaaa')"
if [ "$1" = "start" ] && ipv6_enabled; then
if [ "$(uci -q get 'dhcp.@dnsmasq[0].filter_aaaa')" = "1" ]; then
uci set 'dhcp.@dnsmasq[0]._ssrp_filter_aaaa=1'
uci set 'dhcp.@dnsmasq[0].filter_aaaa=0'
uci commit dhcp
fi
elif [ -n "$saved" ]; then
if [ "$(uci -q get 'dhcp.@dnsmasq[0].filter_aaaa')" = "0" ]; then
uci set "dhcp.@dnsmasq[0].filter_aaaa=$saved"
fi
uci delete 'dhcp.@dnsmasq[0]._ssrp_filter_aaaa'
uci commit dhcp
fi
}
get_filter_aaaa() { get_filter_aaaa() {
ipv6_enabled && { echo 0; return; }
local ret local ret
ret="$(uci get "$NAME.@global[0].filter_aaaa" 2>/dev/null)" ret="$(uci get "$NAME.@global[0].filter_aaaa" 2>/dev/null)"
[ -z "$ret" ] && ret="$(uci get "$NAME.@global[0].mosdns_ipv6" 2>/dev/null)" [ -z "$ret" ] && ret="$(uci get "$NAME.@global[0].mosdns_ipv6" 2>/dev/null)"
@@ -636,7 +668,8 @@ prepare_clash_runtime_config() {
external-controller: 127.0.0.1:$CLASH_API_PORT external-controller: 127.0.0.1:$CLASH_API_PORT
secret: $clash_secret secret: $clash_secret
allow-lan: true allow-lan: true
bind-address: 0.0.0.0 bind-address: "$(ipv6_enabled && echo '*' || echo '0.0.0.0')"
ipv6: $(ipv6_enabled && echo true || echo false)
routing-mark: $CLASH_OUTBOUND_MARK routing-mark: $CLASH_OUTBOUND_MARK
profile: profile:
store-selected: true store-selected: true
@@ -780,7 +813,12 @@ get_host_ip() {
ip="$host" ip="$host"
elif ! echo "$host" | grep -q ':'; then elif ! echo "$host" | grep -q ':'; then
ip=$(resolveip -4 -t 3 "$host" | awk 'NR==1{print}') ip=$(resolveip -4 -t 3 "$host" | awk 'NR==1{print}')
if [ -z "$ip" ] && ipv6_enabled; then
ip=$(resolveip -6 -t 3 "$host" | awk 'NR==1{print}')
fi
[ -z "$ip" ] && ip=$(curl -sSL "http://119.29.29.29/d?dn=$host" | awk -F ';' '{print $1}') [ -z "$ip" ] && ip=$(curl -sSL "http://119.29.29.29/d?dn=$host" | awk -F ';' '{print $1}')
elif ipv6_enabled; then
ip="$host"
fi fi
[ -z "$ip" ] || uci_set_by_name "$1" ip "$ip" [ -z "$ip" ] || uci_set_by_name "$1" ip "$ip"
[ -n "$ip" ] || ip="" [ -n "$ip" ] || ip=""
@@ -1000,7 +1038,7 @@ start_socks5_udp_with_ipt2socks() {
-k "$(uci_get_by_name "$sid" password)" -k "$(uci_get_by_name "$sid" password)"
fi fi
for _i in $(seq 1 "$threads"); do for _i in $(seq 1 "$threads"); do
ln_start_bin "$ipt2socks_bin" ipt2socks -U -r -b 0.0.0.0 -4 \ ln_start_bin "$ipt2socks_bin" ipt2socks -U -r $(ipt2socks_listen_args) \
-s "$server_ip" \ -s "$server_ip" \
-p "$(uci_get_by_name "$sid" server_port)" \ -p "$(uci_get_by_name "$sid" server_port)" \
-l "$local_port" "$@" -l "$local_port" "$@"
@@ -1032,7 +1070,7 @@ start_socks5_redir_with_ipt2socks() {
fi fi
for _i in $(seq 1 "$threads"); do for _i in $(seq 1 "$threads"); do
ln_start_bin "$ipt2socks_bin" ipt2socks \ ln_start_bin "$ipt2socks_bin" ipt2socks \
-T -R -r -b 0.0.0.0 -4 \ -T -R -r $(ipt2socks_listen_args) \
-s "$server_ip" \ -s "$server_ip" \
-p "$(uci_get_by_name "$sid" server_port)" \ -p "$(uci_get_by_name "$sid" server_port)" \
-l "$local_port" "$@" -l "$local_port" "$@"
@@ -1224,6 +1262,12 @@ normalize_xray_protocol_nodes() {
} }
add_dns_into_ipset() { add_dns_into_ipset() {
if [ "$USE_TABLES" = "nftables" ]; then
# GFW mode must proxy the resolver before any domain can populate the set.
# Other nft modes already proxy destinations outside the bypass sets.
[ "$1" = "gfw" ] && nft add element inet ss_spec gfwlist "{ ${2%:*} }" 2>/dev/null
return 0
fi
case "$1" in case "$1" in
gfw) ipset add gfwlist "${2%:*}" 2>/dev/null ;; gfw) ipset add gfwlist "${2%:*}" 2>/dev/null ;;
*) ipset add ss_spec_wan_ac "${2%:*}" nomatch 2>/dev/null ;; *) ipset add ss_spec_wan_ac "${2%:*}" nomatch 2>/dev/null ;;
@@ -1281,10 +1325,8 @@ start_dns() {
if [ "$builtin_dns_enabled" = "1" ]; then if [ "$builtin_dns_enabled" = "1" ]; then
pdnsd_enable_flag=7 pdnsd_enable_flag=7
elif [ "$ssrplus_dns" != "0" ]; then elif [ "$ssrplus_dns" != "0" ]; then
if [ "$USE_TABLES" = "iptables" ]; then if [ -n "$dnsserver" ]; then
if [ -n "$dnsserver" ]; then add_dns_into_ipset "$run_mode" "$dnsserver"
add_dns_into_ipset "$run_mode" "$dnsserver"
fi
fi fi
case "$ssrplus_dns" in case "$ssrplus_dns" in
1) 1)
@@ -1297,9 +1339,7 @@ start_dns() {
output=$(for i in $(echo "$mosdns_dnsserver" | sed "s/,/ /g"); do output=$(for i in $(echo "$mosdns_dnsserver" | sed "s/,/ /g"); do
dnsserver="${i%:*}" dnsserver="${i%:*}"
dnsserver="${i##*/}" dnsserver="${i##*/}"
if [ "$USE_TABLES" = "iptables" ]; then add_dns_into_ipset "$run_mode" "$dnsserver"
add_dns_into_ipset "$run_mode" "$dnsserver"
fi
echo " - addr: $i" echo " - addr: $i"
echo " enable_pipeline: true" echo " enable_pipeline: true"
done) done)
@@ -1783,7 +1823,7 @@ start_udp() {
trojan) #client trojan) #client
gen_config_file "$UDP_RELAY_SERVER" "$type" 2 "$tmp_udp_local_port" gen_config_file "$UDP_RELAY_SERVER" "$type" 2 "$tmp_udp_local_port"
ln_start_bin "$(first_type trojan)" "$type" --config "$udp_config_file" ln_start_bin "$(first_type trojan)" "$type" --config "$udp_config_file"
ln_start_bin "$(first_type ipt2socks)" ipt2socks -U -b 0.0.0.0 -4 -s 127.0.0.1 -p "$tmp_udp_local_port" -l "$tmp_udp_port" ln_start_bin "$(first_type ipt2socks)" ipt2socks -U $(ipt2socks_listen_args) -s 127.0.0.1 -p "$tmp_udp_local_port" -l "$tmp_udp_port"
echolog "UDP TPROXY Relay:$($(first_type trojan) --version 2>&1 | head -1) Started!" echolog "UDP TPROXY Relay:$($(first_type trojan) --version 2>&1 | head -1) Started!"
;; ;;
naiveproxy) naiveproxy)
@@ -2994,6 +3034,11 @@ start() {
mkdir -p /var/run /var/lock /var/log "$DNSMASQ_CONF_DIR" "$TMP_BIN_PATH" "$TMP_DNSMASQ_PATH" "$CLASH_CONFIG_DIR" mkdir -p /var/run /var/lock /var/log "$DNSMASQ_CONF_DIR" "$TMP_BIN_PATH" "$TMP_DNSMASQ_PATH" "$CLASH_CONFIG_DIR"
echo "conf-dir=${TMP_DNSMASQ_PATH}" >"$DNSMASQ_CONF_DIR/dnsmasq-ssrplus.conf" echo "conf-dir=${TMP_DNSMASQ_PATH}" >"$DNSMASQ_CONF_DIR/dnsmasq-ssrplus.conf"
check_run_environment check_run_environment
if [ "$(uci_get_by_type global ipv6_support 0)" = "1" ] && [ "$USE_TABLES" != "nftables" ]; then
echolog "ERROR: IPv6 透明代理需要 nftables 后端。"
unset_lock
return 1
fi
normalize_run_mode >/dev/null normalize_run_mode >/dev/null
normalize_xray_protocol_nodes normalize_xray_protocol_nodes
@@ -3003,8 +3048,18 @@ start() {
Start_Run Start_Run
start_http_proxy start_http_proxy
start_xhttp_addr start_xhttp_addr
start_rules if ! start_rules; then
echolog "ERROR: 无法安装透明代理规则,启动中止。"
unset_lock
stop
return 1
fi
set_ipv6_dns_filter start
start_dns start_dns
# Include resolver bootstrap addresses in the firewall reload snapshot.
if [ "$USE_TABLES" = "nftables" ]; then
/usr/bin/ssr-rules -P >/dev/null 2>&1
fi
# Restore ipsets after rules creation # Restore ipsets after rules creation
if [ "$USE_TABLES" = "iptables" ]; then if [ "$USE_TABLES" = "iptables" ]; then
for setname in gfwlist china blacklist whitelist; do for setname in gfwlist china blacklist whitelist; do
@@ -3186,6 +3241,7 @@ stop() {
uci -q del "dhcp.@dnsmasq[0]._unused_ssrp_changed" uci -q del "dhcp.@dnsmasq[0]._unused_ssrp_changed"
uci -q commit "dhcp" uci -q commit "dhcp"
fi fi
set_ipv6_dns_filter stop
if [ -f "$DNSMASQ_CONF_DIR/dnsmasq-ssrplus.conf" ]; then if [ -f "$DNSMASQ_CONF_DIR/dnsmasq-ssrplus.conf" ]; then
rm -rf "$DNSMASQ_CONF_DIR/dnsmasq-ssrplus.conf" \ rm -rf "$DNSMASQ_CONF_DIR/dnsmasq-ssrplus.conf" \
"$TMP_DNSMASQ_PATH" \ "$TMP_DNSMASQ_PATH" \
File diff suppressed because it is too large. Load diff
+22 -4
View File
@@ -8,6 +8,7 @@
# #
. $IPKG_INSTROOT/etc/init.d/shadowsocksr . $IPKG_INSTROOT/etc/init.d/shadowsocksr
. $IPKG_INSTROOT/usr/share/shadowsocksr/ipv6.sh
# Detect firewall version and set appropriate tools # Detect firewall version and set appropriate tools
detect_firewall() { detect_firewall() {
@@ -246,6 +247,7 @@ flush_r() {
} }
flush_nftables() { flush_nftables() {
ipv6_policy_cleanup
# Delete inet ss_spec table # Delete inet ss_spec table
if $NFT list table inet ss_spec >/dev/null 2>&1; then if $NFT list table inet ss_spec >/dev/null 2>&1; then
# Delete all chains # Delete all chains
@@ -618,6 +620,7 @@ ac_rule_nft() {
$NFT add chain inet ss_spec ss_spec_prerouting '{ type nat hook prerouting priority 0; policy accept; }' $NFT add chain inet ss_spec ss_spec_prerouting '{ type nat hook prerouting priority 0; policy accept; }'
fi fi
$NFT flush chain inet ss_spec ss_spec_prerouting 2>/dev/null $NFT flush chain inet ss_spec ss_spec_prerouting 2>/dev/null
$NFT add rule inet ss_spec ss_spec_prerouting meta nfproto ipv6 return
# Exclude special local addresses # Exclude special local addresses
if $NFT list chain inet ss_spec ss_spec_prerouting >/dev/null 2>&1; then if $NFT list chain inet ss_spec ss_spec_prerouting >/dev/null 2>&1; then
@@ -695,6 +698,7 @@ ac_rule_nft() {
$NFT add chain inet ss_spec ss_spec_output '{ type nat hook output priority 0; policy accept; }' $NFT add chain inet ss_spec ss_spec_output '{ type nat hook output priority 0; policy accept; }'
fi fi
$NFT flush chain inet ss_spec ss_spec_output 2>/dev/null $NFT flush chain inet ss_spec ss_spec_output 2>/dev/null
$NFT add rule inet ss_spec ss_spec_output meta nfproto ipv6 return
$NFT add rule inet ss_spec ss_spec_output meta mark 255 return 2>/dev/null $NFT add rule inet ss_spec ss_spec_output meta mark 255 return 2>/dev/null
# Exclude special local addresses # Exclude special local addresses
@@ -720,6 +724,7 @@ ac_rule_nft() {
$NFT add chain inet ss_spec ss_spec_output '{ type nat hook output priority 0; policy accept; }' $NFT add chain inet ss_spec ss_spec_output '{ type nat hook output priority 0; policy accept; }'
fi fi
$NFT flush chain inet ss_spec ss_spec_output 2>/dev/null $NFT flush chain inet ss_spec ss_spec_output 2>/dev/null
$NFT add rule inet ss_spec ss_spec_output meta nfproto ipv6 return
$NFT add rule inet ss_spec ss_spec_output meta mark 255 return 2>/dev/null $NFT add rule inet ss_spec ss_spec_output meta mark 255 return 2>/dev/null
# Exclude special local addresses # Exclude special local addresses
@@ -1309,7 +1314,9 @@ persist_nftables_rules() {
EOF EOF
echo "# Auto-updated: $(date)" >> "$NFTABLES_RULES_FILE" echo "# Auto-updated: $(date)" >> "$NFTABLES_RULES_FILE"
echo "# Runmode: ${RUNMODE:-router}" >> "$NFTABLES_RULES_FILE" echo "# IPv6 schema: 1" >> "$NFTABLES_RULES_FILE"
echo "# IPv6 support: $(uci_get_by_type global ipv6_support 0)" >> "$NFTABLES_RULES_FILE"
echo "# Runmode: ${RUNMODE:-$(normalize_run_mode)}" >> "$NFTABLES_RULES_FILE"
echo "# Server: $server, Port: $local_port" >> "$NFTABLES_RULES_FILE" echo "# Server: $server, Port: $local_port" >> "$NFTABLES_RULES_FILE"
append_nftables_comment "WAN_BP_IP" "$WAN_BP_IP" append_nftables_comment "WAN_BP_IP" "$WAN_BP_IP"
append_nftables_comment "LAN_AC_IP" "$LAN_AC_IP" append_nftables_comment "LAN_AC_IP" "$LAN_AC_IP"
@@ -1464,6 +1471,13 @@ restore_from_persistence() {
return 1 return 1
fi fi
if grep -q 'set gfwlist6 {' "$NFTABLES_RULES_FILE" ||
! grep -q '^# IPv6 schema: 1$' "$NFTABLES_RULES_FILE" ||
! grep -q "^# IPv6 support: $(uci_get_by_type global ipv6_support 0)$" "$NFTABLES_RULES_FILE" || ipv6_enabled; then
loger 6 "Rebuilding IPv6 rules and policy routes"
return 1
fi
loger 6 "Restoring rules from persistence file" loger 6 "Restoring rules from persistence file"
# Cleanup existing rules # Cleanup existing rules
@@ -1471,6 +1485,10 @@ restore_from_persistence() {
# Restore rules from file # Restore rules from file
if $NFT -f "$NFTABLES_RULES_FILE" 2>/dev/null; then if $NFT -f "$NFTABLES_RULES_FILE" 2>/dev/null; then
if $NFT list chain ip ss_spec_mangle ss_spec_tproxy >/dev/null 2>&1; then
ip rule add fwmark "$FWMARK" table 999 priority 999 2>/dev/null
ip route replace local 0.0.0.0/0 dev lo table 999
fi
loger 5 "Rules restored successfully from persistence file" loger 5 "Rules restored successfully from persistence file"
return 0 return 0
else else
@@ -1937,7 +1955,7 @@ if [ -n "$local_port" ]; then
# STEP 7: FORCE_RECREATE priority → must rebuild rules # STEP 7: FORCE_RECREATE priority → must rebuild rules
if [ "$FORCE_RECREATE" = "1" ]; then if [ "$FORCE_RECREATE" = "1" ]; then
loger 5 "Forced regeneration of NFTables rules" loger 5 "Forced regeneration of NFTables rules"
if flush_r && ipset_r && fw_rule && ac_rule && tp_rule && gen_include; then if flush_r && ipset_r && fw_rule && ac_rule && tp_rule && ipv6_rules_apply && gen_include; then
loger 5 "NFT rules applied successfully (forced rebuild)" loger 5 "NFT rules applied successfully (forced rebuild)"
persist_nftables_rules persist_nftables_rules
[ "$ENABLE_AUTO_UPDATE" = "1" ] && start_auto_update_daemon [ "$ENABLE_AUTO_UPDATE" = "1" ] && start_auto_update_daemon
@@ -1963,7 +1981,7 @@ if [ -n "$local_port" ]; then
fi fi
# STEP 9: Persistence doesn't exist or restore failed → generate new rules # STEP 9: Persistence doesn't exist or restore failed → generate new rules
if flush_r && ipset_r && fw_rule && ac_rule && tp_rule && gen_include; then if flush_r && ipset_r && fw_rule && ac_rule && tp_rule && ipv6_rules_apply && gen_include; then
loger 5 "NFTables rules applied successfully" loger 5 "NFTables rules applied successfully"
persist_nftables_rules persist_nftables_rules
[ "$ENABLE_AUTO_UPDATE" = "1" ] && start_auto_update_daemon [ "$ENABLE_AUTO_UPDATE" = "1" ] && start_auto_update_daemon
@@ -1974,7 +1992,7 @@ if [ -n "$local_port" ]; then
fi fi
else else
# iptables # iptables
if flush_r && fw_rule && ipset_r && ac_rule && tp_rule && gen_include; then if flush_r && fw_rule && ipset_r && ac_rule && tp_rule && ipv6_rules_apply && gen_include; then
loger 5 "iptables rules applied successfully" loger 5 "iptables rules applied successfully"
exit 0 exit 0
else else
@@ -8,6 +8,18 @@ check_run_environment
china_ip="${1:-${china_ip:-/etc/ssrplus/china_ssr.txt}}" china_ip="${1:-${china_ip:-/etc/ssrplus/china_ssr.txt}}"
[ -f "$china_ip" ] || exit 1 [ -f "$china_ip" ] || exit 1
if [ "$2" = "6" ]; then
[ "$USE_TABLES" = "nftables" ] || exit 0
nft list set inet ss_spec china6 >/dev/null 2>&1 || exit 0
{
echo 'flush set inet ss_spec china6'
echo 'add element inet ss_spec china6 {'
sed -e 's/#.*//' -e '/^[[:space:]]*$/d' -e 's/$/,/' "$china_ip"
echo '}'
} | nft -f -
exit $?
fi
case "$USE_TABLES" in case "$USE_TABLES" in
nftables) nftables)
skip_inet="${SKIP_INET:-0}" skip_inet="${SKIP_INET:-0}"
@@ -419,7 +419,12 @@ local function get_server_field(sid, option, default)
return value return value
end end
local function ipv6_enabled()
return uci:get_first("shadowsocksr", "global", "ipv6_support", "0") == "1"
end
local function get_filter_aaaa() local function get_filter_aaaa()
if ipv6_enabled() then return "0" end
local value = uci:get_first("shadowsocksr", "global", "filter_aaaa", "1") local value = uci:get_first("shadowsocksr", "global", "filter_aaaa", "1")
if value == nil or value == "" then if value == nil or value == "" then
value = uci:get_first("shadowsocksr", "global", "mosdns_ipv6", "1") value = uci:get_first("shadowsocksr", "global", "mosdns_ipv6", "1")
@@ -485,7 +490,8 @@ local function build_tuic_runtime_doc(sid, local_port, socks_port, mode)
local doc = { local doc = {
["allow-lan"] = true, ["allow-lan"] = true,
["bind-address"] = "0.0.0.0", ["bind-address"] = ipv6_enabled() and "*" or "0.0.0.0",
ipv6 = ipv6_enabled(),
mode = "rule", mode = "rule",
["log-level"] = "silent", ["log-level"] = "silent",
["find-process-mode"] = "off", ["find-process-mode"] = "off",
@@ -950,7 +956,8 @@ local function build_single_proxy_runtime_doc(proxy, local_port, socks_port, mod
local socks_listen = tonumber(socks_port) local socks_listen = tonumber(socks_port)
local doc = { local doc = {
["allow-lan"] = true, ["allow-lan"] = true,
["bind-address"] = "0.0.0.0", ["bind-address"] = ipv6_enabled() and "*" or "0.0.0.0",
ipv6 = ipv6_enabled(),
mode = "rule", mode = "rule",
["log-level"] = "silent", ["log-level"] = "silent",
["find-process-mode"] = "off", ["find-process-mode"] = "off",
@@ -1242,7 +1249,8 @@ local function build_shadowsocks_runtime_doc(sid, local_port, socks_port, mode)
local doc = { local doc = {
["allow-lan"] = true, ["allow-lan"] = true,
["bind-address"] = "0.0.0.0", ["bind-address"] = ipv6_enabled() and "*" or "0.0.0.0",
ipv6 = ipv6_enabled(),
mode = "rule", mode = "rule",
["log-level"] = "silent", ["log-level"] = "silent",
["find-process-mode"] = "off", ["find-process-mode"] = "off",
@@ -60,10 +60,14 @@ local xray_fragment = ucursor:get_all("shadowsocksr", "@global_xray_fragment[0]"
local xray_noise = ucursor:get_all("shadowsocksr", "@xray_noise_packets[0]") or {} local xray_noise = ucursor:get_all("shadowsocksr", "@xray_noise_packets[0]") or {}
local default_node_local_port = ucursor:get_first("shadowsocksr", "global", "default_node_local_port", "1234") local default_node_local_port = ucursor:get_first("shadowsocksr", "global", "default_node_local_port", "1234")
local dns_mode = ucursor:get_first("shadowsocksr", "global", "pdnsd_enable", "0") local dns_mode = ucursor:get_first("shadowsocksr", "global", "pdnsd_enable", "0")
local ipv6_support = ucursor:get_first("shadowsocksr", "global", "ipv6_support", "0") == "1"
local redir_address = ipv6_support and "::" or "0.0.0.0"
local redir_host = ipv6_support and "[::]" or "0.0.0.0"
local dns_ipv4_only = ucursor:get_first("shadowsocksr", "global", "filter_aaaa") local dns_ipv4_only = ucursor:get_first("shadowsocksr", "global", "filter_aaaa")
if not dns_ipv4_only or dns_ipv4_only == "" then if not dns_ipv4_only or dns_ipv4_only == "" then
dns_ipv4_only = ucursor:get_first("shadowsocksr", "global", "mosdns_ipv6", "1") dns_ipv4_only = ucursor:get_first("shadowsocksr", "global", "mosdns_ipv6", "1")
end end
if ipv6_support then dns_ipv4_only = "0" end
local builtin_dns_server = ucursor:get_first("shadowsocksr", "global", "tunnel_forward", "8.8.4.4:53") local builtin_dns_server = ucursor:get_first("shadowsocksr", "global", "tunnel_forward", "8.8.4.4:53")
local outbound_settings = nil local outbound_settings = nil
local xray_version = nil local xray_version = nil
@@ -970,13 +974,13 @@ local trojan = {
} }
local naiveproxy = { local naiveproxy = {
proxy = (server.username and server.password and server.server and server.server_port) and "https://" .. server.username .. ":" .. server.password .. "@" .. format_host_port(server.server, server.server_port), proxy = (server.username and server.password and server.server and server.server_port) and "https://" .. server.username .. ":" .. server.password .. "@" .. format_host_port(server.server, server.server_port),
listen = (proto == "redir") and "redir" .. "://0.0.0.0:" .. tonumber(local_port) or "socks" .. "://0.0.0.0:" .. tonumber(local_port), listen = (proto == "redir") and "redir" .. "://" .. redir_host .. ":" .. tonumber(local_port) or "socks" .. "://0.0.0.0:" .. tonumber(local_port),
["insecure-concurrency"] = tonumber(server.concurrency) or 1 ["insecure-concurrency"] = tonumber(server.concurrency) or 1
} }
local ss = { local ss = {
server = (server.kcp_enable == "1") and "127.0.0.1" or server.server, server = (server.kcp_enable == "1") and "127.0.0.1" or server.server,
server_port = tonumber(server.server_port), server_port = tonumber(server.server_port),
local_address = "0.0.0.0", local_address = redir_address,
local_port = tonumber(local_port), local_port = tonumber(local_port),
mode = (proto == "tcp,udp") and "tcp_and_udp" or (proto .. "_only"), mode = (proto == "tcp,udp") and "tcp_and_udp" or (proto .. "_only"),
password = server.password, password = server.password,
@@ -1039,14 +1043,14 @@ local hysteria2 = {
}, },
--[[ --[[
tcpTProxy = (proto:find("tcp") and local_port ~= "0") and { tcpTProxy = (proto:find("tcp") and local_port ~= "0") and {
listen = "0.0.0.0:" .. tonumber(local_port) listen = redir_host .. ":" .. tonumber(local_port)
} or nil, } or nil,
]]-- ]]--
tcpRedirect = (proto:find("tcp") and local_port ~= "0") and { tcpRedirect = (proto:find("tcp") and local_port ~= "0") and {
listen = "0.0.0.0:" .. tonumber(local_port) listen = redir_host .. ":" .. tonumber(local_port)
} or nil, } or nil,
udpTProxy = (proto:find("udp") and local_port ~= "0") and { udpTProxy = (proto:find("udp") and local_port ~= "0") and {
listen = "0.0.0.0:" .. tonumber(local_port) listen = redir_host .. ":" .. tonumber(local_port)
} or nil, } or nil,
obfs = (server.flag_obfs == "1") and { obfs = (server.flag_obfs == "1") and {
type = server.obfs_type, type = server.obfs_type,
@@ -1124,7 +1128,7 @@ local shadowtls = {
local chain_sslocal = { local chain_sslocal = {
locals = local_port ~= "0" and { locals = local_port ~= "0" and {
{ {
local_address = "0.0.0.0", local_address = redir_address,
local_port = (chain_local_port == "0" and effective_node_local_port or tonumber(chain_local_port)), local_port = (chain_local_port == "0" and effective_node_local_port or tonumber(chain_local_port)),
mode = (proto:find("tcp,udp") and "tcp_and_udp") or proto .. "_only", mode = (proto:find("tcp,udp") and "tcp_and_udp") or proto .. "_only",
protocol = "redir", protocol = "redir",
@@ -1134,12 +1138,12 @@ local chain_sslocal = {
}, },
socks_port ~= "0" and { socks_port ~= "0" and {
protocol = "socks", protocol = "socks",
local_address = "0.0.0.0", local_address = redir_address,
local_port = tonumber(socks_port) local_port = tonumber(socks_port)
} or nil } or nil
} or {{ } or {{
protocol = "socks", protocol = "socks",
local_address = "0.0.0.0", local_address = redir_address,
local_port = tonumber(socks_port) local_port = tonumber(socks_port)
}}, }},
servers = { servers = {
@@ -21,6 +21,14 @@ esac
mkdir -p $TMP_DNSMASQ_PATH mkdir -p $TMP_DNSMASQ_PATH
run_mode=$(normalize_run_mode) run_mode=$(normalize_run_mode)
gfw_set="4#inet#ss_spec#gfwlist"
black_set="4#inet#ss_spec#blacklist"
white_set="4#inet#ss_spec#whitelist_domain"
if ipv6_enabled; then
gfw_set="$gfw_set,6#inet#ss_spec#gfwlist6"
black_set="$black_set,6#inet#ss_spec#blacklist6"
white_set="$white_set,6#inet#ss_spec#whitelist_domain6"
fi
cp -rf /etc/ssrplus/gfw_list.conf $TMP_DNSMASQ_PATH/ cp -rf /etc/ssrplus/gfw_list.conf $TMP_DNSMASQ_PATH/
cp -rf /etc/ssrplus/gfw_base.conf $TMP_DNSMASQ_PATH/ cp -rf /etc/ssrplus/gfw_base.conf $TMP_DNSMASQ_PATH/
@@ -32,12 +40,11 @@ for conf_file in gfw_base.conf gfw_list.conf; do
if [ "$run_mode" = "gfw" ]; then if [ "$run_mode" = "gfw" ]; then
if [ "$nft_support" = "1" ]; then if [ "$nft_support" = "1" ]; then
# gfw + nft:ipset → nftset # gfw + nft:ipset → nftset
sed -i 's|ipset=/\([^/]*\)/\([^[:space:]]*\)|nftset=/\1/inet#ss_spec#\2|g' "$conf" sed -i "s|^ipset=/\\([^/]*\\)/gfwlist$|nftset=/\\1/$gfw_set|" "$conf"
fi fi
else else
# 非 gfw:无条件清理所有分流引用 # 非 gfw:无条件清理所有分流引用
# sed -i '/^[[:space:]]*\(ipset=\|nftset=\)/d' "$conf" sed -i '/^[[:space:]]*\(ipset=\|nftset=\)/d' "$conf"
sed -i '/^[[:space:]]*ipset=/d' "$conf"
fi fi
done done
@@ -65,12 +72,14 @@ for list_file in /etc/ssrplus/black.list /etc/ssrplus/white.list /etc/ssrplus/de
} }
{ {
# 提取 server=/domain/xxx # 提取 server=/domain/xxx
if (match($0, /^server=\/([^\/]+)\//, m)) { if ($0 ~ /^server=\//) {
if (m[1] in domain) next split($0, m, "/")
if (m[2] in domain) next
} }
# 提取 ipset=/domain/xxx # 提取 ipset=/domain/xxx
if (match($0, /^ipset=\/([^\/]+)\//, m)) { if ($0 ~ /^(ipset|nftset)=\//) {
if (m[1] in domain) next split($0, m, "/")
if (m[2] in domain) next
} }
print print
} }
@@ -84,8 +93,8 @@ done
# 此处直接使用 cat 因为有 sed '/#/d' 删除了 数据 # 此处直接使用 cat 因为有 sed '/#/d' 删除了 数据
if [ "$nft_support" = "1" ]; then if [ "$nft_support" = "1" ]; then
sed -e 's/[[:space:]]*#.*$//' -e '/^[[:space:]]*$/d' -e "s/.*/server=\/&\/127.0.0.1#$dns_port\nnftset=\/&\/inet#ss_spec#blacklist/" /etc/ssrplus/black.list > "$TMP_DNSMASQ_PATH/blacklist_forward.conf" sed -e 's/[[:space:]]*#.*$//' -e '/^[[:space:]]*$/d' -e "s/.*/server=\/&\/127.0.0.1#$dns_port\nnftset=\/&\/$black_set/" /etc/ssrplus/black.list > "$TMP_DNSMASQ_PATH/blacklist_forward.conf"
sed -e 's/[[:space:]]*#.*$//' -e '/^[[:space:]]*$/d' -e "s/.*/nftset=\/&\/4#inet#ss_spec#whitelist_domain/" /etc/ssrplus/white.list > "$TMP_DNSMASQ_PATH/whitelist_forward.conf" sed -e 's/[[:space:]]*#.*$//' -e '/^[[:space:]]*$/d' -e "s/.*/nftset=\/&\/$white_set/" /etc/ssrplus/white.list > "$TMP_DNSMASQ_PATH/whitelist_forward.conf"
elif [ "$nft_support" = "0" ]; then elif [ "$nft_support" = "0" ]; then
sed -e 's/[[:space:]]*#.*$//' -e '/^[[:space:]]*$/d' -e "s/.*/server=\/&\/127.0.0.1#$dns_port\nipset=\/&\/blacklist/" /etc/ssrplus/black.list > "$TMP_DNSMASQ_PATH/blacklist_forward.conf" sed -e 's/[[:space:]]*#.*$//' -e '/^[[:space:]]*$/d' -e "s/.*/server=\/&\/127.0.0.1#$dns_port\nipset=\/&\/blacklist/" /etc/ssrplus/black.list > "$TMP_DNSMASQ_PATH/blacklist_forward.conf"
sed -e 's/[[:space:]]*#.*$//' -e '/^[[:space:]]*$/d' -e "s/.*/ipset=\/&\/whitelist/" /etc/ssrplus/white.list > "$TMP_DNSMASQ_PATH/whitelist_forward.conf" sed -e 's/[[:space:]]*#.*$//' -e '/^[[:space:]]*$/d' -e "s/.*/ipset=\/&\/whitelist/" /etc/ssrplus/white.list > "$TMP_DNSMASQ_PATH/whitelist_forward.conf"
@@ -117,12 +126,14 @@ if [ "$(uci_get_by_type global adblock 0)" == "1" ]; then
{ {
keep = 1 keep = 1
# 精确匹配 server=/domain/ # 精确匹配 server=/domain/
if (match($0, /^server=\/([^\/]+)\//, m)) { if ($0 ~ /^server=\//) {
if (m[1] in domain) keep = 0 split($0, m, "/")
if (m[2] in domain) keep = 0
} }
# 精确匹配 ipset=/domain/ # 精确匹配 ipset=/domain/
if (match($0, /^ipset=\/([^\/]+)\//, m)) { if ($0 ~ /^(ipset|nftset)=\//) {
if (m[1] in domain) keep = 0 split($0, m, "/")
if (m[2] in domain) keep = 0
} }
if (keep) print if (keep) print
} }
+192
View File
@@ -0,0 +1,192 @@
#!/bin/sh
# IPv6 nftables rules, sourced by ssr-rules. DNS sets are shared by TCP and UDP.
# br_netfilter can run inet prerouting before ipv6_rcv() orphans the skb,
# losing the TPROXY socket on bridged LAN ingress. Routed fw4 filtering still
# runs when this bridge-only compatibility hook is disabled.
ipv6_bridge_nf() {
local knob=/proc/sys/net/bridge/bridge-nf-call-ip6tables
local saved=/var/run/ssrplus-ipv6-bridge-nf
[ -f "$knob" ] || return 0
if [ "$1" = "start" ]; then
[ -f "$saved" ] || cat "$knob" > "$saved"
echo 0 > "$knob"
elif [ -f "$saved" ]; then
[ "$(cat "$knob")" != "0" ] || cat "$saved" > "$knob"
rm -f "$saved"
fi
}
ipv6_policy_route() {
ipv6_bridge_nf start || return 1
ip -6 rule show | grep -q "fwmark ${FWMARK}.*lookup 999" ||
ip -6 rule add fwmark "$FWMARK" table 999 priority 999 || return 1
ip -6 route replace local ::/0 dev lo table 999
}
ipv6_policy_cleanup() {
ipv6_bridge_nf stop
while ip -6 rule del fwmark "$FWMARK" table 999 2>/dev/null; do :; done
ip -6 route del local ::/0 dev lo table 999 2>/dev/null
return 0
}
ipv6_set_elements() {
local setname="$1" address
shift
for address in "$@"; do
case "$address" in
*:*) printf 'add element inet ss_spec %s { %s }\n' "$setname" "$address" ;;
esac
done
}
ipv6_policy_rules() {
local chain="$1" target="$2"
cat <<-RULES
add rule inet ss_spec $chain meta mark 255 return
add rule inet ss_spec $chain fib daddr type local return
add rule inet ss_spec $chain ip6 daddr @local6 return
add rule inet ss_spec $chain ip6 daddr @servers6 return
add rule inet ss_spec $chain ip6 daddr @blacklist6 goto $target
add rule inet ss_spec $chain ip6 daddr @whitelist6 return
add rule inet ss_spec $chain ip6 daddr @whitelist_domain6 return
add rule inet ss_spec $chain ip6 saddr @fplan6 goto $target
add rule inet ss_spec $chain ip6 saddr @bplan6 return
RULES
case "$RUNMODE" in
gfw)
echo "add rule inet ss_spec $chain ip6 daddr @china6 return"
echo "add rule inet ss_spec $chain ip6 daddr @gfwlist6 goto $target"
echo "add rule inet ss_spec $chain ip6 saddr @gmlan6 goto $target"
;;
router)
echo "add rule inet ss_spec $chain ip6 daddr @china6 return"
echo "add rule inet ss_spec $chain goto $target"
;;
all) echo "add rule inet ss_spec $chain goto $target" ;;
esac
}
ipv6_lan_hook() {
local chain="$1" proto="$2" target="$3" match="" name ifname
case "${LAN_AC_IP%%${LAN_AC_IP#?}}" in
w|W) match="ip6 saddr @lan_ac6" ;;
b|B) match="ip6 saddr != @lan_ac6" ;;
esac
if [ -z "$Interface" ]; then
echo "add rule inet ss_spec $chain meta nfproto ipv6 meta l4proto $proto $match jump $target"
else
for name in $Interface; do
ifname=$(uci -P /var/state -q get "network.$name.device")
[ -n "$ifname" ] || ifname=$(uci -P /var/state -q get "network.$name.device")
[ -n "$ifname" ] || continue
echo "add rule inet ss_spec $chain iifname \"$ifname\" meta nfproto ipv6 meta l4proto $proto $match jump $target"
done
fi
}
ipv6_rules_generate() {
local setname sid host ports
for setname in china6 whitelist6 blacklist6 fplan6 bplan6 gmlan6 lan_ac6 servers6; do
echo "add set inet ss_spec $setname { type ipv6_addr; flags interval; auto-merge; }"
done
for setname in gfwlist6 whitelist_domain6; do
echo "add set inet ss_spec $setname { type ipv6_addr; }"
done
cat <<-'RULES'
add set inet ss_spec local6 { type ipv6_addr; flags interval; auto-merge; elements = { ::/128, ::1/128, ::ffff:0:0/96, 64:ff9b:1::/48, 100::/64, 2001:db8::/32, fc00::/7, fe80::/10, ff00::/8 }; }
RULES
if [ -s /etc/ssrplus/china6_ssr.txt ]; then
echo 'add element inet ss_spec china6 {'
sed -e 's/#.*//' -e '/^[[:space:]]*$/d' -e 's/$/,/' /etc/ssrplus/china6_ssr.txt
echo '}'
fi
ipv6_set_elements whitelist6 $WAN_BP_IP
ipv6_set_elements blacklist6 $WAN_FW_IP
ipv6_set_elements fplan6 $LAN_FP_IP
ipv6_set_elements bplan6 $LAN_BP_IP
ipv6_set_elements gmlan6 $LAN_GM_IP
ipv6_set_elements lan_ac6 ${LAN_AC_IP#?}
for sid in "$(uci_get_by_type global global_server)" "$(uci_get_by_type global udp_relay_server)"; do
host=$(uci_get_by_name "$sid" server)
host="${host#\[}"
host="${host%\]}"
[ -n "$host" ] || continue
case "$host" in
*:*) ipv6_set_elements servers6 "$host" ;;
*) ipv6_set_elements servers6 $(resolveip -6 -t 3 "$host" 2>/dev/null) ;;
esac
done
ports=$(echo "$PROXY_PORTS" | sed 's/-m multiport --dports //; s/:/-/g')
cat <<-'RULES'
add chain inet ss_spec ss_spec6_tcp
add chain inet ss_spec ss_spec6_redirect
add chain inet ss_spec ss_spec6_prerouting { type nat hook prerouting priority -1; policy accept; }
RULES
[ -z "$ports" ] || echo "add rule inet ss_spec ss_spec6_redirect tcp dport != { $ports } return"
echo "add rule inet ss_spec ss_spec6_redirect meta l4proto tcp counter redirect to :$local_port"
ipv6_policy_rules ss_spec6_tcp ss_spec6_redirect
ipv6_lan_hook ss_spec6_prerouting tcp ss_spec6_tcp
if [ "$OUTPUT" = "1" ] || [ "$OUTPUT" = "2" ]; then
echo 'add chain inet ss_spec ss_spec6_output { type nat hook output priority -1; policy accept; }'
if [ "$OUTPUT" = "2" ]; then
cat <<-'RULES'
add rule inet ss_spec ss_spec6_output meta mark 255 return
add rule inet ss_spec ss_spec6_output fib daddr type local return
add rule inet ss_spec ss_spec6_output ip6 daddr @local6 return
add rule inet ss_spec ss_spec6_output ip6 daddr @servers6 return
add rule inet ss_spec ss_spec6_output meta nfproto ipv6 meta l4proto tcp jump ss_spec6_redirect
RULES
else
echo 'add rule inet ss_spec ss_spec6_output meta nfproto ipv6 meta l4proto tcp jump ss_spec6_tcp'
fi
fi
[ "$DISABLE_UDP_RULES" = "1" ] && return 0
cat <<-'RULES'
add chain inet ss_spec ss_spec6_udp
add chain inet ss_spec ss_spec6_udp_proxy
add chain inet ss_spec ss_spec6_mangle { type filter hook prerouting priority mangle; policy accept; }
add rule inet ss_spec ss_spec6_mangle udp dport 53 return
RULES
if [ -n "$TPROXY" ]; then
[ -z "$ports" ] || echo "add rule inet ss_spec ss_spec6_udp_proxy udp dport != { $ports } return"
echo "add rule inet ss_spec ss_spec6_udp_proxy meta l4proto udp counter meta mark set $FWMARK tproxy ip6 to :$LOCAL_PORT accept"
echo "add rule inet ss_spec ss_spec6_mangle meta nfproto ipv6 meta l4proto udp meta mark $FWMARK jump ss_spec6_udp_proxy"
else
echo 'add rule inet ss_spec ss_spec6_udp_proxy udp dport 443 counter drop'
fi
ipv6_policy_rules ss_spec6_udp ss_spec6_udp_proxy
ipv6_lan_hook ss_spec6_mangle udp ss_spec6_udp
if [ -n "$TPROXY" ] && { [ "$OUTPUT" = "1" ] || [ "$OUTPUT" = "2" ]; }; then
cat <<-'RULES'
add chain inet ss_spec ss_spec6_udp_output { type route hook output priority mangle; policy accept; }
add chain inet ss_spec ss_spec6_udp_mark
add chain inet ss_spec ss_spec6_udp_local
add rule inet ss_spec ss_spec6_udp_output udp dport 53 return
add rule inet ss_spec ss_spec6_udp_output meta nfproto ipv6 meta l4proto udp jump ss_spec6_udp_local
RULES
[ -z "$ports" ] || echo "add rule inet ss_spec ss_spec6_udp_mark udp dport != { $ports } return"
echo "add rule inet ss_spec ss_spec6_udp_mark counter meta mark set $FWMARK return"
if [ "$OUTPUT" = "2" ]; then
local RUNMODE=all
fi
ipv6_policy_rules ss_spec6_udp_local ss_spec6_udp_mark
fi
}
ipv6_rules_apply() {
[ "$USE_NFT" = "1" ] && ipv6_enabled || return 0
local rules_file
rules_file=$(mktemp /tmp/ssr-ipv6.XXXXXX) || return 1
if ! ipv6_rules_generate > "$rules_file" || ! $NFT -f "$rules_file"; then
rm -f "$rules_file"
loger 3 "Failed to install IPv6 proxy rules"
return 1
fi
rm -f "$rules_file"
if [ -n "$TPROXY" ] && [ "$DISABLE_UDP_RULES" != "1" ]; then
ipv6_policy_route
fi
}
@@ -7,6 +7,7 @@ config global
option custom_ports '80,443' option custom_ports '80,443'
option pdnsd_enable '1' option pdnsd_enable '1'
option filter_aaaa '1' option filter_aaaa '1'
option ipv6_support '0'
option tunnel_forward '8.8.4.4:53' option tunnel_forward '8.8.4.4:53'
option monitor_enable '1' option monitor_enable '1'
option enable_switch '1' option enable_switch '1'
@@ -16,6 +17,7 @@ config global
option default_node_local_port '1234' option default_node_local_port '1234'
option component_mirror 'direct' option component_mirror 'direct'
option gfwlist_url 'https://fastly.jsdelivr.net/gh/YW5vbnltb3Vz/domain-list-community@release/gfwlist.txt' option gfwlist_url 'https://fastly.jsdelivr.net/gh/YW5vbnltb3Vz/domain-list-community@release/gfwlist.txt'
option chnroute6_url 'https://raw.githubusercontent.com/1715173329/IPCIDR-CHINA/master/ipv6.txt'
option chnroute_url 'https://ispip.clang.cn/all_cn.txt' option chnroute_url 'https://ispip.clang.cn/all_cn.txt'
option adblock_url 'https://anti-ad.net/anti-ad-for-dnsmasq.conf' option adblock_url 'https://anti-ad.net/anti-ad-for-dnsmasq.conf'
@@ -151,6 +151,29 @@ local function update(url, file, type, file2)
local refresh_cmd = "curl -sSL --insecure -o /tmp/ssr-update." .. type .. " " .. url local refresh_cmd = "curl -sSL --insecure -o /tmp/ssr-update." .. type .. " " .. url
local sret = luci.sys.call(refresh_cmd) local sret = luci.sys.call(refresh_cmd)
if sret == 0 then if sret == 0 then
if type == "ip6_data" then
local ip = require "luci.ip"
local entries = {}
for line in io.lines("/tmp/ssr-update." .. type) do
line = line:gsub("#.*$", ""):match("^%s*(.-)%s*$")
if line ~= "" then
if not ip.IPv6(line) then
os.remove("/tmp/ssr-update." .. type)
log(-1)
return
end
table.insert(entries, line)
end
end
if #entries == 0 then
os.remove("/tmp/ssr-update." .. type)
log(-1)
return
end
local out = io.open("/tmp/ssr-update." .. type, "w")
out:write(table.concat(entries, "\n"), "\n")
out:close()
end
if type == "gfw_data" then if type == "gfw_data" then
local gfwlist = io.open("/tmp/ssr-update." .. type, "r") local gfwlist = io.open("/tmp/ssr-update." .. type, "r")
local decode = gfwlist:read("*a") local decode = gfwlist:read("*a")
@@ -210,7 +233,9 @@ local function update(url, file, type, file2)
if file2 then if file2 then
luci.sys.exec("cp -f /tmp/ssr-update." .. type .. " " .. file2) luci.sys.exec("cp -f /tmp/ssr-update." .. type .. " " .. file2)
end end
if type == "gfw_data" or type == "ad_data" then if type == "ip6_data" then
luci.sys.call("/usr/share/shadowsocksr/chinaipset.sh /etc/ssrplus/china6_ssr.txt 6")
elseif type == "gfw_data" or type == "ad_data" then
luci.sys.call("/usr/share/shadowsocksr/gfw2ipset.sh") luci.sys.call("/usr/share/shadowsocksr/gfw2ipset.sh")
else else
if luci.sys.call("command -v ipset >/dev/null 2>&1") == 0 then if luci.sys.call("command -v ipset >/dev/null 2>&1") == 0 then
@@ -233,7 +258,17 @@ local function update(url, file, type, file2)
os.remove("/tmp/ssr-update." .. type) os.remove("/tmp/ssr-update." .. type)
end end
local function update_ipv6()
update(uci:get_first("shadowsocksr", "global", "chnroute6_url",
"https://raw.githubusercontent.com/1715173329/IPCIDR-CHINA/master/ipv6.txt"),
"/etc/ssrplus/china6_ssr.txt", "ip6_data")
end
if args then if args then
if args == "ip6_data" then
update_ipv6()
os.exit(0)
end
if args == "gfw_data" then if args == "gfw_data" then
update(uci:get_first("shadowsocksr", "global", "gfwlist_url"), "/etc/ssrplus/gfw_list.conf", args, TMP_DNSMASQ_PATH .. "/gfw_list.conf") update(uci:get_first("shadowsocksr", "global", "gfwlist_url"), "/etc/ssrplus/gfw_list.conf", args, TMP_DNSMASQ_PATH .. "/gfw_list.conf")
os.exit(0) os.exit(0)
@@ -253,6 +288,10 @@ if args then
else else
log("正在更新【GFW列表】数据库") log("正在更新【GFW列表】数据库")
update(uci:get_first("shadowsocksr", "global", "gfwlist_url"), "/etc/ssrplus/gfw_list.conf", "gfw_data", TMP_DNSMASQ_PATH .. "/gfw_list.conf") update(uci:get_first("shadowsocksr", "global", "gfwlist_url"), "/etc/ssrplus/gfw_list.conf", "gfw_data", TMP_DNSMASQ_PATH .. "/gfw_list.conf")
if uci:get_first("shadowsocksr", "global", "ipv6_support", "0") == "1" then
log("正在更新【国内IPv6段】数据库")
update_ipv6()
end
log("正在更新【国内IP段】数据库") log("正在更新【国内IP段】数据库")
update(uci:get_first("shadowsocksr", "global", "chnroute_url"), "/etc/ssrplus/china_ssr.txt", "ip_data", TMP_PATH .. "/china_ssr.txt") update(uci:get_first("shadowsocksr", "global", "chnroute_url"), "/etc/ssrplus/china_ssr.txt", "ip_data", TMP_PATH .. "/china_ssr.txt")
if uci:get_first("shadowsocksr", "global", "apple_optimization", "0") == "1" then if uci:get_first("shadowsocksr", "global", "apple_optimization", "0") == "1" then
+25
View File
@@ -0,0 +1,25 @@
#!/usr/bin/env node
// Use the real LuCI CBI dependency evaluator, including checkbox empty values.
const fs = require('fs');
const vm = require('vm');
const assert = require('assert');
const path = require('path');
const cbi = fs.readFileSync(process.argv[2], 'utf8');
const client = fs.readFileSync(path.join(__dirname, '../luasrc/model/cbi/shadowsocksr/client.lua'), 'utf8');
const filter = client.slice(client.indexOf('o = s:option(Flag, "filter_aaaa"'), client.indexOf('if is_finded("chinadns-ng") then', client.indexOf('o = s:option(Flag, "filter_aaaa"')));
const deps = [...filter.matchAll(/o:depends\(\{pdnsd_enable = "(\d+)", ipv6_support = (false|"0")\}\)/g)]
.map(m => ({ pdnsd_enable: m[1], ipv6_support: m[2] === 'false' ? false : '0' }));
assert.strictEqual(deps.length, 3);
const fields = { pdnsd_enable: { type: 'select', value: '1' }, ipv6_support: { type: 'checkbox', value: '1', checked: false } };
const ctx = { document: { querySelectorAll: query => [fields[query.match(/id="([^"]+)"/)[1]]] } };
vm.createContext(ctx);
vm.runInContext(cbi.slice(cbi.indexOf('function cbi_d_checkvalue('), cbi.indexOf('function cbi_d_update(')), ctx);
for (const mode of ['0', '1', '4', '6', '7']) {
fields.pdnsd_enable.value = mode;
for (const checked of [false, true, false]) {
fields.ipv6_support.checked = checked;
assert.strictEqual(ctx.cbi_d_check(deps), !checked && ['1', '4', '7'].includes(mode));
}
}
assert.match(filter, /function o\.remove\(self, section\)\s*end/);
console.log('PASS: actual CBI checkbox dependencies toggle immediately and preserve the saved filter');
+67
View File
@@ -0,0 +1,67 @@
#!/usr/bin/env python3
"""Exercise DNS generation with BusyBox awk, both backends and all run modes."""
import os
from pathlib import Path
import shutil
import subprocess
import tempfile
source = Path(__file__).resolve().parents[1] / 'root/usr/share/shadowsocksr/gfw2ipset.sh'
with tempfile.TemporaryDirectory(prefix='ssr6-dns-') as tmp:
root = Path(tmp)
config = root / 'config'
output = root / 'output'
init = root / 'etc/init.d'
for directory in (config, output, init, root / 'bin'):
directory.mkdir(parents=True)
for name in ('gfw_base.conf', 'gfw_list.conf'):
(config / name).write_text(''.join(
f'server=/{domain}/127.0.0.1#5335\nipset=/{domain}/gfwlist\n'
for domain in ('proxy.example', 'white.example', 'black.example', 'deny.example')))
for name, domain in [('white', 'white'), ('black', 'black'), ('deny', 'deny')]:
(config / f'{name}.list').write_text(f'# comment\n\n{domain}.example\n')
(init / 'shadowsocksr').write_text('''
check_run_environment() { USE_TABLES="$BACKEND"; }
echolog() { :; }
normalize_run_mode() { echo "$MODE"; }
ipv6_enabled() { [ "$V6" = 1 ] && [ "$USE_TABLES" = nftables ]; }
uci_get_by_type() { echo 0; }
TMP_DNSMASQ_PATH="$TEST_OUTPUT"
dns_port=5335
''')
script = root / 'generate.sh'
script.write_text(source.read_text().replace('/etc/ssrplus/', str(config) + '/'))
busybox = shutil.which('busybox')
if not busybox:
raise SystemExit('BusyBox is required to test the OpenWrt awk dialect')
(root / 'bin/awk').symlink_to(busybox)
count = 0
for backend in ('nftables', 'iptables'):
for mode in ('gfw', 'router', 'all'):
for ipv6 in ('0', '1'):
env = dict(os.environ, BACKEND=backend, MODE=mode, V6=ipv6,
IPKG_INSTROOT=tmp, TEST_OUTPUT=str(output),
PATH=str(root / 'bin') + ':' + os.environ['PATH'])
subprocess.run([busybox, 'sh', str(script)], env=env, check=True)
gfw = (output / 'gfw_list.conf').read_text()
white = (output / 'whitelist_forward.conf').read_text()
black = (output / 'blacklist_forward.conf').read_text()
assert 'proxy.example' in gfw
for domain in ('white.example', 'black.example', 'deny.example'):
assert domain not in gfw, (backend, mode, ipv6, domain)
assert 'server=' not in white
assert 'server=/black.example/127.0.0.1#5335' in black
if backend == 'nftables':
assert '4#inet#ss_spec#whitelist_domain' in white
assert ('6#inet#ss_spec#whitelist_domain6' in white) == (ipv6 == '1')
assert ('6#inet#ss_spec#blacklist6' in black) == (ipv6 == '1')
if mode == 'gfw':
assert '4#inet#ss_spec#gfwlist' in gfw
assert ('6#inet#ss_spec#gfwlist6' in gfw) == (ipv6 == '1')
else:
assert 'nftset=' not in gfw and 'ipset=' not in gfw
else:
assert 'nftset=' not in gfw + white + black
assert ('ipset=/proxy.example/gfwlist' in gfw) == (mode == 'gfw')
count += 1
print(f'PASS: {count} DNS generation combinations, domain overrides and AAAA sets')
+49
View File
@@ -0,0 +1,49 @@
#!/bin/sh
# Run as root on OpenWrt with the package installed. Rules stay in a new netns.
set -eu
ns="ssr6-check-$$"
tmp=$(mktemp -d)
cleanup() {
ip netns del "$ns" 2>/dev/null || true
rm -rf "$tmp"
}
trap cleanup EXIT INT TERM
ip netns add "$ns"
. /usr/share/shadowsocksr/ipv6.sh
uci_get_by_type() { :; }
uci_get_by_name() { :; }
FWMARK=0x53535250
local_port=1234
LOCAL_PORT=301
Interface=""
LAN_AC_IP="w192.0.2.1 2001:db8::1"
WAN_BP_IP="192.0.2.2 2001:db8:1::/64"
WAN_FW_IP="192.0.2.3 2001:db8:2::/64"
LAN_BP_IP="2001:db8::2"
LAN_FP_IP="2001:db8::3"
LAN_GM_IP="2001:db8::4"
count=0
for RUNMODE in gfw router all; do
for OUTPUT in 0 1 2; do
for udp in tproxy quic disabled; do
TPROXY=""
DISABLE_UDP_RULES=0
case "$udp" in
tproxy) TPROXY=2 ;;
disabled) DISABLE_UDP_RULES=1 ;;
esac
for PROXY_PORTS in "" "-m multiport --dports 53,80,443,8000:8100"; do
ip netns exec "$ns" nft flush ruleset
ip netns exec "$ns" nft add table inet ss_spec
ipv6_rules_generate > "$tmp/rules"
ip netns exec "$ns" nft -f "$tmp/rules"
# Validate the serialized form used during a firewall reload.
ip netns exec "$ns" nft list ruleset > "$tmp/snapshot"
ip netns exec "$ns" nft flush ruleset
ip netns exec "$ns" nft -f "$tmp/snapshot"
count=$((count + 1))
done
done
done
done
printf 'PASS: %s IPv6 mode/OUTPUT/UDP/port combinations and snapshot reloads\n' "$count"
@@ -583,8 +583,8 @@ function getConfigCipherValue(section_id, hwtype) {
function getDisplayEncryption(radioNet) { function getDisplayEncryption(radioNet) {
const hwtype = uci.get('wireless', radioNet.getWifiDeviceName(), 'type'); const hwtype = uci.get('wireless', radioNet.getWifiDeviceName(), 'type');
const configured = getConfigEncryptionValue(radioNet.getName(), hwtype); const configured = getConfigEncryptionValue(radioNet.getName(), hwtype);
// The QCA iwinfo backend reports OWE as WPA2-PSK. // The QCA iwinfo backend reports OWE and SAE as WPA2-PSK.
if (isQcaWifiHwtype(hwtype) && configured == 'owe') if (isQcaWifiHwtype(hwtype) && ['owe', 'sae', 'sae-mixed'].includes(configured))
return formatConfigEncryption(configured); return formatConfigEncryption(configured);
const encryption = radioNet.getActiveEncryption(); const encryption = radioNet.getActiveEncryption();
@@ -3301,6 +3301,10 @@ return view.extend({
} }
else if (e == 'sae') { else if (e == 'sae') {
uci.set('wireless', section_id, 'sae', '1'); uci.set('wireless', section_id, 'sae', '1');
if (getSecurityBand() == '6g') {
uci.set('wireless', section_id, 'sae_pwe', '1');
uci.set('wireless', section_id, 'ieee80211w', '2');
}
} }
else { else {
uci.unset('wireless', section_id, 'sae'); uci.unset('wireless', section_id, 'sae');