🔥 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
+1655 -74

No files matched your search

+1 -1
View File
@@ -7,7 +7,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-honk
PKG_VERSION:=1.1.4
PKG_RELEASE:=15
PKG_RELEASE:=16
LUCI_TITLE:=LuCI support for honk
LUCI_PKGARCH:=all
+58 -21
View File
@@ -91,9 +91,8 @@ function strip_dae_comments(content) {
}
function parse_native_api(clean_content) {
let api_m = match(clean_content, /native_api\s*\{([^}]+)\}/);
if (!api_m) return null;
let block = api_m[1];
let block = extract_bracket_block(clean_content, /native_api\s*\{/);
if (!block) return null;
let listen_m = match(block, /listen\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 new_lines = [];
let start_idx = -1;
let end_idx = -1;
let in_block = false;
let depth = 0;
let in_single = false;
let in_double = false;
for (let idx, line in lines) {
let trimmed = trim(line);
if (!in_block && match(trimmed, header_regex)) {
in_block = true;
let open_cnt = length(split(trimmed, "{")) - 1;
let close_cnt = length(split(trimmed, "}")) - 1;
depth = open_cnt - close_cnt;
if (open_cnt > 0 && depth <= 0) {
in_block = false;
}
continue;
start_idx = idx;
}
if (in_block) {
let open_cnt = length(split(trimmed, "{")) - 1;
let close_cnt = length(split(trimmed, "}")) - 1;
depth += (open_cnt - close_cnt);
let len = length(line);
for (let i = 0; i < len; i++) {
let c = substr(line, i, 1);
let next_c = (i + 1 < len) ? substr(line, i + 1, 1) : "";
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) {
in_block = false;
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);
}
@@ -221,9 +255,12 @@ function clean_legacy_api_from_config(config_file) {
if (!main_content) return;
let cleaned_main = remove_bracket_block(main_content, /^[#\/]*\s*clash_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 inner_clean = strip_dae_comments(exp_m[1]);
let exp_block = extract_bracket_block(cleaned_main, /experimental\s*\{/);
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/)) {
cleaned_main = remove_bracket_block(cleaned_main, /^[#\/]*\s*experimental\s*\{/);
}
+2 -2
View File
@@ -1,8 +1,8 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-open-box
PKG_VERSION:=0.1.292
PKG_RELEASE:=15
PKG_VERSION:=0.1.293
PKG_RELEASE:=16
PKG_MAINTAINER:=liandu2024
PKG_LICENSE:=GPL-3.0-or-later
+1 -1
View File
@@ -4,7 +4,7 @@ LUCI_TITLE:=luci-app-ssr-plus
LUCI_PKGARCH:=all
PKG_NAME:=luci-app-ssr-plus
PKG_VERSION:=196
PKG_RELEASE:=84
PKG_RELEASE:=85
PKG_CONFIG_DEPENDS:= \
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.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.rmempty = false
o.default = "1"
@@ -108,6 +108,17 @@ o:value("router", translate("IP Route Mode"))
o:value("all", translate("Global Mode"))
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:value("1", translate("All 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 = s:option(Flag, "filter_aaaa", translate("Disable IPv6 for Overseas FQDN"))
o:depends("pdnsd_enable", "1")
o:depends("pdnsd_enable", "4")
o:depends("pdnsd_enable", "7")
o:depends({pdnsd_enable = "1", ipv6_support = false})
o:depends({pdnsd_enable = "4", ipv6_support = false})
o:depends({pdnsd_enable = "7", ipv6_support = false})
o.rmempty = false
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
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)"))
@@ -21,10 +21,10 @@ o.description = translate("Listen only on the given interface or, if unspecified
s:tab("wan_ac", translate("WAN IP AC"))
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.datatype = "ip4addr"
o.datatype = "ipaddr"
-- Part of LAN
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.datatype = "ipaddr"
luci.ip.neighbors({family = 4}, function(entry)
luci.ip.neighbors({}, function(entry)
if entry.reachable then
o:value(entry.dest:string())
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.datatype = "ipaddr"
luci.ip.neighbors({family = 4}, function(entry)
luci.ip.neighbors({}, function(entry)
if entry.reachable then
o:value(entry.dest:string())
end
@@ -55,7 +55,7 @@ end)
o = s:taboption("lan_ac", DynamicList, "lan_fp_ips", translate("LAN Force Proxy Host List"))
o.datatype = "ipaddr"
luci.ip.neighbors({family = 4}, function(entry)
luci.ip.neighbors({}, function(entry)
if entry.reachable then
o:value(entry.dest:string())
end
@@ -63,7 +63,7 @@ end)
o = s:taboption("lan_ac", DynamicList, "lan_gm_ips", translate("Game Mode Host List"))
o.datatype = "ipaddr"
luci.ip.neighbors({family = 4}, function(entry)
luci.ip.neighbors({}, function(entry)
if entry.reachable then
o:value(entry.dest:string())
end
@@ -266,6 +266,11 @@ s.rawhtml = true
s.template = "shadowsocksr/refresh"
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
s = m:field(DummyValue, "apple_data", translate("Apple Domains Data"))
s.rawhtml = true
+15
View File
@@ -3870,3 +3870,18 @@ msgstr "空闲会话超时(秒)"
msgid "Minimum idle sessions"
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 地址库"
+65 -9
View File
@@ -127,7 +127,39 @@ uci_get_by_cfgid() {
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() {
ipv6_enabled && { echo 0; return; }
local ret
ret="$(uci get "$NAME.@global[0].filter_aaaa" 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
secret: $clash_secret
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
profile:
store-selected: true
@@ -780,7 +813,12 @@ get_host_ip() {
ip="$host"
elif ! echo "$host" | grep -q ':'; then
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}')
elif ipv6_enabled; then
ip="$host"
fi
[ -z "$ip" ] || uci_set_by_name "$1" ip "$ip"
[ -n "$ip" ] || ip=""
@@ -1000,7 +1038,7 @@ start_socks5_udp_with_ipt2socks() {
-k "$(uci_get_by_name "$sid" password)"
fi
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" \
-p "$(uci_get_by_name "$sid" server_port)" \
-l "$local_port" "$@"
@@ -1032,7 +1070,7 @@ start_socks5_redir_with_ipt2socks() {
fi
for _i in $(seq 1 "$threads"); do
ln_start_bin "$ipt2socks_bin" ipt2socks \
-T -R -r -b 0.0.0.0 -4 \
-T -R -r $(ipt2socks_listen_args) \
-s "$server_ip" \
-p "$(uci_get_by_name "$sid" server_port)" \
-l "$local_port" "$@"
@@ -1224,6 +1262,12 @@ normalize_xray_protocol_nodes() {
}
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
gfw) ipset add gfwlist "${2%:*}" 2>/dev/null ;;
*) ipset add ss_spec_wan_ac "${2%:*}" nomatch 2>/dev/null ;;
@@ -1281,11 +1325,9 @@ start_dns() {
if [ "$builtin_dns_enabled" = "1" ]; then
pdnsd_enable_flag=7
elif [ "$ssrplus_dns" != "0" ]; then
if [ "$USE_TABLES" = "iptables" ]; then
if [ -n "$dnsserver" ]; then
add_dns_into_ipset "$run_mode" "$dnsserver"
fi
fi
case "$ssrplus_dns" in
1)
start_dns2tcp "$dnsserver"
@@ -1297,9 +1339,7 @@ start_dns() {
output=$(for i in $(echo "$mosdns_dnsserver" | sed "s/,/ /g"); do
dnsserver="${i%:*}"
dnsserver="${i##*/}"
if [ "$USE_TABLES" = "iptables" ]; then
add_dns_into_ipset "$run_mode" "$dnsserver"
fi
echo " - addr: $i"
echo " enable_pipeline: true"
done)
@@ -1783,7 +1823,7 @@ start_udp() {
trojan) #client
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 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!"
;;
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"
echo "conf-dir=${TMP_DNSMASQ_PATH}" >"$DNSMASQ_CONF_DIR/dnsmasq-ssrplus.conf"
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_xray_protocol_nodes
@@ -3003,8 +3048,18 @@ start() {
Start_Run
start_http_proxy
start_xhttp_addr
start_rules
if ! start_rules; then
echolog "ERROR: 无法安装透明代理规则,启动中止。"
unset_lock
stop
return 1
fi
set_ipv6_dns_filter start
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
if [ "$USE_TABLES" = "iptables" ]; then
for setname in gfwlist china blacklist whitelist; do
@@ -3186,6 +3241,7 @@ stop() {
uci -q del "dhcp.@dnsmasq[0]._unused_ssrp_changed"
uci -q commit "dhcp"
fi
set_ipv6_dns_filter stop
if [ -f "$DNSMASQ_CONF_DIR/dnsmasq-ssrplus.conf" ]; then
rm -rf "$DNSMASQ_CONF_DIR/dnsmasq-ssrplus.conf" \
"$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/usr/share/shadowsocksr/ipv6.sh
# Detect firewall version and set appropriate tools
detect_firewall() {
@@ -246,6 +247,7 @@ flush_r() {
}
flush_nftables() {
ipv6_policy_cleanup
# Delete inet ss_spec table
if $NFT list table inet ss_spec >/dev/null 2>&1; then
# 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; }'
fi
$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
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; }'
fi
$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
# 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; }'
fi
$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
# Exclude special local addresses
@@ -1309,7 +1314,9 @@ persist_nftables_rules() {
EOF
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"
append_nftables_comment "WAN_BP_IP" "$WAN_BP_IP"
append_nftables_comment "LAN_AC_IP" "$LAN_AC_IP"
@@ -1464,6 +1471,13 @@ restore_from_persistence() {
return 1
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"
# Cleanup existing rules
@@ -1471,6 +1485,10 @@ restore_from_persistence() {
# Restore rules from file
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"
return 0
else
@@ -1937,7 +1955,7 @@ if [ -n "$local_port" ]; then
# STEP 7: FORCE_RECREATE priority → must rebuild rules
if [ "$FORCE_RECREATE" = "1" ]; then
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)"
persist_nftables_rules
[ "$ENABLE_AUTO_UPDATE" = "1" ] && start_auto_update_daemon
@@ -1963,7 +1981,7 @@ if [ -n "$local_port" ]; then
fi
# 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"
persist_nftables_rules
[ "$ENABLE_AUTO_UPDATE" = "1" ] && start_auto_update_daemon
@@ -1974,7 +1992,7 @@ if [ -n "$local_port" ]; then
fi
else
# 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"
exit 0
else
@@ -8,6 +8,18 @@ check_run_environment
china_ip="${1:-${china_ip:-/etc/ssrplus/china_ssr.txt}}"
[ -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
nftables)
skip_inet="${SKIP_INET:-0}"
@@ -419,7 +419,12 @@ local function get_server_field(sid, option, default)
return value
end
local function ipv6_enabled()
return uci:get_first("shadowsocksr", "global", "ipv6_support", "0") == "1"
end
local function get_filter_aaaa()
if ipv6_enabled() then return "0" end
local value = uci:get_first("shadowsocksr", "global", "filter_aaaa", "1")
if value == nil or value == "" then
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 = {
["allow-lan"] = true,
["bind-address"] = "0.0.0.0",
["bind-address"] = ipv6_enabled() and "*" or "0.0.0.0",
ipv6 = ipv6_enabled(),
mode = "rule",
["log-level"] = "silent",
["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 doc = {
["allow-lan"] = true,
["bind-address"] = "0.0.0.0",
["bind-address"] = ipv6_enabled() and "*" or "0.0.0.0",
ipv6 = ipv6_enabled(),
mode = "rule",
["log-level"] = "silent",
["find-process-mode"] = "off",
@@ -1242,7 +1249,8 @@ local function build_shadowsocks_runtime_doc(sid, local_port, socks_port, mode)
local doc = {
["allow-lan"] = true,
["bind-address"] = "0.0.0.0",
["bind-address"] = ipv6_enabled() and "*" or "0.0.0.0",
ipv6 = ipv6_enabled(),
mode = "rule",
["log-level"] = "silent",
["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 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 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")
if not dns_ipv4_only or dns_ipv4_only == "" then
dns_ipv4_only = ucursor:get_first("shadowsocksr", "global", "mosdns_ipv6", "1")
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 outbound_settings = nil
local xray_version = nil
@@ -970,13 +974,13 @@ local trojan = {
}
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),
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
}
local ss = {
server = (server.kcp_enable == "1") and "127.0.0.1" or server.server,
server_port = tonumber(server.server_port),
local_address = "0.0.0.0",
local_address = redir_address,
local_port = tonumber(local_port),
mode = (proto == "tcp,udp") and "tcp_and_udp" or (proto .. "_only"),
password = server.password,
@@ -1039,14 +1043,14 @@ local hysteria2 = {
},
--[[
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,
]]--
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,
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,
obfs = (server.flag_obfs == "1") and {
type = server.obfs_type,
@@ -1124,7 +1128,7 @@ local shadowtls = {
local chain_sslocal = {
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)),
mode = (proto:find("tcp,udp") and "tcp_and_udp") or proto .. "_only",
protocol = "redir",
@@ -1134,12 +1138,12 @@ local chain_sslocal = {
},
socks_port ~= "0" and {
protocol = "socks",
local_address = "0.0.0.0",
local_address = redir_address,
local_port = tonumber(socks_port)
} or nil
} or {{
protocol = "socks",
local_address = "0.0.0.0",
local_address = redir_address,
local_port = tonumber(socks_port)
}},
servers = {
@@ -21,6 +21,14 @@ esac
mkdir -p $TMP_DNSMASQ_PATH
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_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 [ "$nft_support" = "1" ]; then
# 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
else
# 非 gfw:无条件清理所有分流引用
# sed -i '/^[[:space:]]*\(ipset=\|nftset=\)/d' "$conf"
sed -i '/^[[:space:]]*ipset=/d' "$conf"
sed -i '/^[[:space:]]*\(ipset=\|nftset=\)/d' "$conf"
fi
done
@@ -65,12 +72,14 @@ for list_file in /etc/ssrplus/black.list /etc/ssrplus/white.list /etc/ssrplus/de
}
{
# 提取 server=/domain/xxx
if (match($0, /^server=\/([^\/]+)\//, m)) {
if (m[1] in domain) next
if ($0 ~ /^server=\//) {
split($0, m, "/")
if (m[2] in domain) next
}
# 提取 ipset=/domain/xxx
if (match($0, /^ipset=\/([^\/]+)\//, m)) {
if (m[1] in domain) next
if ($0 ~ /^(ipset|nftset)=\//) {
split($0, m, "/")
if (m[2] in domain) next
}
print
}
@@ -84,8 +93,8 @@ done
# 此处直接使用 cat 因为有 sed '/#/d' 删除了 数据
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/.*/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/.*/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=\/&\/$white_set/" /etc/ssrplus/white.list > "$TMP_DNSMASQ_PATH/whitelist_forward.conf"
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/.*/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
# 精确匹配 server=/domain/
if (match($0, /^server=\/([^\/]+)\//, m)) {
if (m[1] in domain) keep = 0
if ($0 ~ /^server=\//) {
split($0, m, "/")
if (m[2] in domain) keep = 0
}
# 精确匹配 ipset=/domain/
if (match($0, /^ipset=\/([^\/]+)\//, m)) {
if (m[1] in domain) keep = 0
if ($0 ~ /^(ipset|nftset)=\//) {
split($0, m, "/")
if (m[2] in domain) keep = 0
}
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 pdnsd_enable '1'
option filter_aaaa '1'
option ipv6_support '0'
option tunnel_forward '8.8.4.4:53'
option monitor_enable '1'
option enable_switch '1'
@@ -16,6 +17,7 @@ config global
option default_node_local_port '1234'
option component_mirror 'direct'
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 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 sret = luci.sys.call(refresh_cmd)
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
local gfwlist = io.open("/tmp/ssr-update." .. type, "r")
local decode = gfwlist:read("*a")
@@ -210,7 +233,9 @@ local function update(url, file, type, file2)
if file2 then
luci.sys.exec("cp -f /tmp/ssr-update." .. type .. " " .. file2)
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")
else
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)
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 == "ip6_data" then
update_ipv6()
os.exit(0)
end
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")
os.exit(0)
@@ -253,6 +288,10 @@ if args then
else
log("正在更新【GFW列表】数据库")
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段】数据库")
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
+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) {
const hwtype = uci.get('wireless', radioNet.getWifiDeviceName(), 'type');
const configured = getConfigEncryptionValue(radioNet.getName(), hwtype);
// The QCA iwinfo backend reports OWE as WPA2-PSK.
if (isQcaWifiHwtype(hwtype) && configured == 'owe')
// The QCA iwinfo backend reports OWE and SAE as WPA2-PSK.
if (isQcaWifiHwtype(hwtype) && ['owe', 'sae', 'sae-mixed'].includes(configured))
return formatConfigEncryption(configured);
const encryption = radioNet.getActiveEncryption();
@@ -3301,6 +3301,10 @@ return view.extend({
}
else if (e == 'sae') {
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 {
uci.unset('wireless', section_id, 'sae');