diff --git a/luci-app-honk/Makefile b/luci-app-honk/Makefile index 6c428c287..34fc157dc 100644 --- a/luci-app-honk/Makefile +++ b/luci-app-honk/Makefile @@ -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 diff --git a/luci-app-honk/root/usr/share/rpcd/ucode/honk.uc b/luci-app-honk/root/usr/share/rpcd/ucode/honk.uc index a684634af..97213e411 100644 --- a/luci-app-honk/root/usr/share/rpcd/ucode/honk.uc +++ b/luci-app-honk/root/usr/share/rpcd/ucode/honk.uc @@ -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); - if (depth <= 0) { - in_block = false; + 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) { + 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*\{/); } diff --git a/luci-app-open-box/Makefile b/luci-app-open-box/Makefile index dd7b076cd..7314e7897 100644 --- a/luci-app-open-box/Makefile +++ b/luci-app-open-box/Makefile @@ -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 diff --git a/luci-app-ssr-plus/Makefile b/luci-app-ssr-plus/Makefile index b495b4581..f3acbd1f2 100644 --- a/luci-app-ssr-plus/Makefile +++ b/luci-app-ssr-plus/Makefile @@ -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 \ diff --git a/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua b/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua index a8e4a216c..a4f2a8107 100644 --- a/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua +++ b/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua @@ -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" diff --git a/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua b/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua index 7ec558deb..6fd43bed0 100644 --- a/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua +++ b/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua @@ -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)")) diff --git a/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua b/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua index 0d92887ff..b565465ee 100644 --- a/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua +++ b/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/control.lua @@ -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 diff --git a/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua b/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua index b037e20d3..b7ef7a268 100644 --- a/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua +++ b/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua @@ -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 diff --git a/luci-app-ssr-plus/po/zh_Hans/ssr-plus.po b/luci-app-ssr-plus/po/zh_Hans/ssr-plus.po index 04f34d5e7..831ac7fe1 100644 --- a/luci-app-ssr-plus/po/zh_Hans/ssr-plus.po +++ b/luci-app-ssr-plus/po/zh_Hans/ssr-plus.po @@ -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 地址库" diff --git a/luci-app-ssr-plus/root/etc/init.d/shadowsocksr b/luci-app-ssr-plus/root/etc/init.d/shadowsocksr index a112956c4..8ee875552 100755 --- a/luci-app-ssr-plus/root/etc/init.d/shadowsocksr +++ b/luci-app-ssr-plus/root/etc/init.d/shadowsocksr @@ -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,10 +1325,8 @@ 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 + if [ -n "$dnsserver" ]; then + add_dns_into_ipset "$run_mode" "$dnsserver" fi case "$ssrplus_dns" in 1) @@ -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 + add_dns_into_ipset "$run_mode" "$dnsserver" 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" \ diff --git a/luci-app-ssr-plus/root/etc/ssrplus/china6_ssr.txt b/luci-app-ssr-plus/root/etc/ssrplus/china6_ssr.txt new file mode 100644 index 000000000..09ff972ac --- /dev/null +++ b/luci-app-ssr-plus/root/etc/ssrplus/china6_ssr.txt @@ -0,0 +1,1019 @@ +2001:250::/30 +2001:254::/31 +2001:256:100::/48 +2001:c68::/32 +2001:cc0::/32 +2001:da8::/32 +2001:daa:1::/48 +2001:daa:2::/47 +2001:daa:4::/47 +2001:daa:6::/48 +2001:daa:9::/48 +2001:dc7::/32 +2001:dd8:1::/48 +2001:dd9::/48 +2001:df6:40::/48 +2001:4510:400::/40 +2400:1160::/32 +2400:3200::/32 +2400:5280:f803::/48 +2400:5a00::/32 +2400:6000::/32 +2400:6600::/32 +2400:6e60:1301::/48 +2400:75aa::/32 +2400:7fc0::/40 +2400:7fc0:220::/44 +2400:7fc0:240::/44 +2400:7fc0:2a0::/44 +2400:7fc0:2c0::/44 +2400:7fc0:4000::/40 +2400:7fc0:4100::/48 +2400:7fc0:6000::/40 +2400:7fc0:8000::/36 +2400:7fc0:a000::/36 +2400:7fc0:bb00::/40 +2400:7fc0:c000::/36 +2400:8200::/32 +2400:89c0:1010::/44 +2400:89c0:1050::/46 +2400:89c0:1150::/48 +2400:89c0:6000::/48 +2400:8fc0::/32 +2400:9020:f010::/46 +2400:9380:8001::/48 +2400:9380:8003::/48 +2400:9380:8021::/48 +2400:9380:8040::/48 +2400:9380:8140::/48 +2400:9380:8201::/48 +2400:9380:8301::/48 +2400:9380:9001::/48 +2400:9380:9009::/48 +2400:9380:9020::/47 +2400:9380:9050::/47 +2400:9380:9060::/48 +2400:9380:9071::/48 +2400:9380:9080::/47 +2400:9380:90b1::/48 +2400:9380:90b2::/47 +2400:9380:90b4::/46 +2400:9380:9109::/48 +2400:9380:9121::/48 +2400:9380:9220::/47 +2400:9380:9250::/47 +2400:9380:9260::/48 +2400:9380:9271::/48 +2400:9380:9280::/47 +2400:9380:9282::/48 +2400:9380:92b1::/48 +2400:9380:92b2::/47 +2400:9380:92b4::/46 +2400:95e0::/48 +2400:9600:8800::/48 +2400:9620::/32 +2400:a860:1::/48 +2400:a860:2::/47 +2400:a860:4::/47 +2400:a860:6::/48 +2400:a980::/29 +2400:ae00:1981::/48 +2400:b200::/32 +2400:b600::/32 +2400:b620::/48 +2400:b700::/48 +2400:be00::/48 +2400:cb80:e2e::/47 +2400:cb80:e30::/44 +2400:cb80:e40::/44 +2400:da00::/32 +2400:dd00::/28 +2400:ee00:ffec::/46 +2400:ee00:fff0::/44 +2400:f6e0::/32 +2400:f720::/32 +2400:fe00::/32 +2401:800::/32 +2401:1160::/32 +2401:1320::/32 +2401:1d40::/32 +2401:2a00:f000::/43 +2401:2e00::/32 +2401:3480::/36 +2401:3480:2000::/48 +2401:3480:3000::/36 +2401:34a0::/31 +2401:3800::/32 +2401:5c20:10::/48 +2401:71c0::/48 +2401:7660::/48 +2401:7e00::/32 +2401:8be0::/48 +2401:9a00::/44 +2401:9a00:10::/46 +2401:a140:1::/48 +2401:b180::/32 +2401:b400::/45 +2401:b400:8::/47 +2401:b400:11::/48 +2401:b400:14::/48 +2401:b400:16::/47 +2401:b400:20::/47 +2401:b680::/32 +2401:be00::/32 +2401:c020:6::/48 +2401:c020:8::/47 +2401:c020:14::/48 +2401:cb80::/32 +2401:cc00::/32 +2401:ce00::/32 +2401:d0e0:3::/48 +2401:d920::/48 +2401:de00::/32 +2401:ec00::/32 +2401:fa00:40::/48 +2402:840:d000::/46 +2402:840:e000::/46 +2402:840:f000::/38 +2402:c60::/48 +2402:1440::/32 +2402:2000::/32 +2402:36e0::/44 +2402:4b80::/32 +2402:4e00::/32 +2402:5ec0::/32 +2402:6e80::/32 +2402:6f40::/48 +2402:6f40:2::/47 +2402:7d80::/48 +2402:7d80:240::/48 +2402:7d80:8888::/48 +2402:8bc0::/32 +2402:92c0::/48 +2402:93c0::/48 +2402:93c0:20::/48 +2402:93c0:100::/48 +2402:9a80::/32 +2402:a200::/32 +2402:b8c0:6::/48 +2402:b8c0:86::/48 +2402:b8c0:106::/48 +2402:b8c0:186::/48 +2402:d340::/32 +2402:db40:5100::/48 +2402:db40:5f00::/46 +2402:dfc0::/44 +2402:dfc0:50::/44 +2402:e480::/32 +2402:e740::/32 +2402:e7c0::/32 +2402:e880::/48 +2402:ef40::/32 +2402:f000::/32 +2402:f140:ff00::/46 +2402:f140:ff04::/48 +2402:f140:ff10::/46 +2402:f140:ff14::/48 +2402:f8c0::/42 +2403:600::/32 +2403:c80::/32 +2403:1b80::/48 +2403:1ec0:1200::/48 +2403:1ec0:1400::/48 +2403:1ec0:1600::/48 +2403:1ec0:1610::/48 +2403:2040::/48 +2403:2b40::/32 +2403:3140::/32 +2403:4280::/47 +2403:4300::/32 +2403:4c80::/48 +2403:5c80::/48 +2403:6740::/48 +2403:6a00::/32 +2403:7580::/32 +2403:8080:101::/48 +2403:8c00::/32 +2403:9b00:2400::/48 +2403:9b00:6000::/48 +2403:a100::/48 +2403:a140:10::/48 +2403:a140:100::/40 +2403:a200::/32 +2403:ac00::/32 +2403:b400::/32 +2403:c980::/32 +2403:d400::/32 +2403:e7c0:3::/48 +2403:f4c0::/48 +2403:ffc0:1100::/40 +2403:ffc0:1200::/39 +2404:bc0:1::/48 +2404:bc0:4000::/43 +2404:bc0:4100::/43 +2404:bc0:4200::/43 +2404:bc0:4300::/44 +2404:bc0:4400::/43 +2404:bc0:4500::/43 +2404:bc0:4620::/44 +2404:bc0:4f00::/43 +2404:1c80::/32 +2404:3700::/48 +2404:4dc0::/32 +2404:6380::/48 +2404:6380:1000::/48 +2404:6380:8001::/48 +2404:6500:dcb3::/48 +2404:7180:a000::/48 +2404:7180:a010::/48 +2404:7180:aa00::/48 +2404:7180:aa10::/48 +2404:7180:b001::/48 +2404:7180:b002::/48 +2404:7180:b010::/48 +2404:7180:c001::/48 +2404:7180:c002::/48 +2404:7180:c011::/48 +2404:7180:c012::/48 +2404:7180:f000::/48 +2404:7180:f010::/48 +2404:7240::/33 +2404:7600::/32 +2404:7940::/32 +2404:c2c0::/40 +2404:c2c0:111::/48 +2404:c2c0:112::/48 +2404:c2c0:240::/44 +2404:c2c0:280::/44 +2404:c2c0:2c0::/44 +2404:c2c0:501::/48 +2404:c2c0:4000::/40 +2404:c2c0:4100::/48 +2404:c2c0:6000::/40 +2404:c2c0:8000::/36 +2404:c2c0:bb00::/40 +2404:c2c0:c000::/36 +2404:c300::/32 +2404:c940::/48 +2404:e280::/47 +2404:e8c0::/32 +2405:80:10::/48 +2405:80:13::/48 +2405:6c0:2::/48 +2405:6c0:4::/48 +2405:1480:1000::/48 +2405:1480:2000::/48 +2405:1480:3000::/47 +2405:3140:11::/48 +2405:3140:21::/48 +2405:3140:31::/48 +2405:3140:3a::/48 +2405:68c0:21::/48 +2405:6940::/48 +2405:6f00:c101::/48 +2405:6f00:c102::/48 +2405:6f00:c170::/47 +2405:6f00:c602::/48 +2405:7040:6000::/47 +2405:78c0:6e00::/43 +2405:8a40::/32 +2405:a900:ffee::/48 +2405:ad00::/32 +2405:b7c0::/32 +2405:d900::/32 +2405:e000::/32 +2405:f940::/32 +2406:280::/32 +2406:840:f889::/48 +2406:1080::/48 +2406:2700::/32 +2406:3340::/32 +2406:4d00::/48 +2406:52c0::/32 +2406:5340:6666::/48 +2406:5340:8888::/48 +2406:6100::/32 +2406:b640:100::/48 +2406:b640:4100::/48 +2406:cac0::/40 +2406:cac0:200::/40 +2406:cf00::/48 +2406:cf00:1000::/43 +2406:d440:100::/43 +2406:d440:200::/43 +2406:d440:300::/43 +2406:d440:ff00::/48 +2406:d440:ffff::/48 +2406:e3c0::/32 +2406:e500::/33 +2407:23c0::/48 +2407:2840::/48 +2407:37c0::/32 +2407:5380::/32 +2407:6c40:1100::/48 +2407:6c40:1210::/48 +2407:6c40:1500::/48 +2407:6c40:1600::/40 +2407:7d00:1::/48 +2407:8f40:2::/48 +2407:9f00::/32 +2407:ad80::/32 +2407:ae80::/32 +2407:b380:8000::/48 +2407:bc00::/32 +2407:c080::/35 +2407:c080:4000::/37 +2407:c080:5000::/37 +2407:c080:6000::/36 +2407:c080:8000::/36 +2407:d9c0::/32 +2408:4000::/22 +2408:8000::/48 +2408:8000:2::/47 +2408:8000:1000::/36 +2408:8000:2000::/35 +2408:8000:4000::/34 +2408:8000:8000::/33 +2408:8001::/32 +2408:8024:8000::/33 +2408:8025::/32 +2408:8026::/32 +2408:802a:8000::/33 +2408:802c::/32 +2408:803e::/32 +2408:8056::/32 +2408:80c2::/33 +2408:80c5::/33 +2408:80e0:4000::/36 +2408:80e2::/33 +2408:80e9:4000::/34 +2408:80f0:4000::/36 +2408:80f1::/42 +2408:80f1:40::/43 +2408:80f1:70::/44 +2408:80f1:80::/41 +2408:80f1:100::/43 +2408:80f1:120::/44 +2408:80f1:160::/43 +2408:80f1:180::/43 +2408:80f1:1b0::/44 +2408:80f1:1c0::/43 +2408:80f1:1e0::/44 +2408:80f1:200::/40 +2408:80f5:4000::/34 +2408:80f9:4000::/34 +2408:8120:1::/48 +2408:8120:2::/48 +2408:8120:7000::/36 +2408:8140:2000::/48 +2408:815f:e000::/35 +2408:8181::/40 +2408:8181:6000::/40 +2408:8181:8000::/40 +2408:8181:a000::/40 +2408:8181:a220::/44 +2408:8181:e000::/40 +2408:8182:6000::/40 +2408:8182:c000::/40 +2408:8183:4000::/40 +2408:8183:8000::/40 +2408:81a2:2000::/35 +2408:81a2:4000::/35 +2408:81a3:6000::/35 +2408:81a3:c800::/48 +2408:81a3:ca66::/48 +2408:8206::/31 +2408:8208::/29 +2408:8210::/30 +2408:8214::/31 +2408:821a::/31 +2408:8220::/31 +2408:8226::/32 +2408:822a::/31 +2408:822e::/31 +2408:8230::/29 +2408:8238::/31 +2408:823c::/31 +2408:8240::/32 +2408:8244::/30 +2408:8248::/30 +2408:824c::/32 +2408:824e::/31 +2408:8250::/30 +2408:8254::/32 +2408:8256::/31 +2408:8258::/30 +2408:825c::/31 +2408:825f::/32 +2408:8260::/32 +2408:8262::/31 +2408:8264::/31 +2408:8266::/32 +2408:826a::/32 +2408:826c::/30 +2408:8270::/32 +2408:8274::/30 +2408:8278::/31 +2408:827a::/32 +2408:8306::/31 +2408:8308::/30 +2408:8310::/30 +2408:832a::/32 +2408:832e::/31 +2408:8330::/30 +2408:8338::/32 +2408:8340::/32 +2408:8344::/30 +2408:8348::/30 +2408:834e::/31 +2408:8350::/30 +2408:8354::/32 +2408:8356::/31 +2408:8358::/30 +2408:8360::/30 +2408:8364::/31 +2408:836c::/30 +2408:8374::/30 +2408:8378::/31 +2408:837a::/32 +2408:8406:100::/41 +2408:8406:180::/42 +2408:8406:d00::/41 +2408:8406:d80::/42 +2408:8406:1900::/41 +2408:8406:1980::/42 +2408:8406:2500::/41 +2408:8406:2580::/42 +2408:8406:3100::/41 +2408:8406:3180::/42 +2408:8406:3d00::/41 +2408:8406:3d80::/42 +2408:8406:4900::/41 +2408:8406:4980::/42 +2408:8406:5500::/41 +2408:8406:5580::/42 +2408:8406:6100::/41 +2408:8406:6180::/42 +2408:8406:6d00::/41 +2408:8406:6d80::/42 +2408:8406:7900::/41 +2408:8406:7980::/42 +2408:8406:8500::/41 +2408:8406:8580::/42 +2408:8406:9100::/41 +2408:8406:9180::/42 +2408:8406:9d00::/41 +2408:8406:9d80::/42 +2408:8406:a900::/41 +2408:8406:a980::/42 +2408:8406:b500::/41 +2408:8406:b580::/42 +2408:8407:520::/43 +2408:8409::/40 +2408:8409:100::/41 +2408:8409:1a0::/43 +2408:8409:1c0::/42 +2408:8409:200::/42 +2408:8409:c00::/40 +2408:8409:d00::/41 +2408:8409:da0::/43 +2408:8409:dc0::/42 +2408:8409:e00::/42 +2408:8409:1800::/40 +2408:8409:1900::/41 +2408:8409:19a0::/43 +2408:8409:19c0::/42 +2408:8409:1a00::/42 +2408:8409:2400::/40 +2408:8409:2500::/41 +2408:8409:25a0::/43 +2408:8409:25c0::/42 +2408:8409:2600::/42 +2408:8409:3000::/40 +2408:8409:3100::/41 +2408:8409:31a0::/43 +2408:8409:31c0::/42 +2408:8409:3200::/42 +2408:8409:3c00::/40 +2408:8409:3d00::/41 +2408:8409:3da0::/43 +2408:8409:3dc0::/42 +2408:8409:3e00::/42 +2408:8409:4800::/40 +2408:8409:4900::/41 +2408:8409:49a0::/43 +2408:8409:49c0::/42 +2408:8409:4a00::/42 +2408:8409:5400::/40 +2408:8409:5500::/41 +2408:8409:55a0::/43 +2408:8409:55c0::/42 +2408:8409:5600::/42 +2408:8409:6000::/40 +2408:8409:6100::/41 +2408:8409:61a0::/43 +2408:8409:61c0::/42 +2408:8409:6200::/42 +2408:8409:6c00::/40 +2408:8409:6d00::/41 +2408:8409:6da0::/43 +2408:8409:6dc0::/42 +2408:8409:6e00::/42 +2408:8409:7800::/40 +2408:8409:7900::/41 +2408:8409:79a0::/43 +2408:8409:79c0::/42 +2408:8409:7a00::/42 +2408:8409:8400::/40 +2408:8409:8500::/41 +2408:8409:85a0::/43 +2408:8409:85c0::/42 +2408:8409:8600::/42 +2408:8409:9000::/40 +2408:8409:9100::/41 +2408:8409:91a0::/43 +2408:8409:91c0::/42 +2408:8409:9200::/42 +2408:8409:9c00::/40 +2408:8409:9d00::/41 +2408:8409:9da0::/43 +2408:8409:9dc0::/42 +2408:8409:9e00::/42 +2408:8409:a800::/40 +2408:8409:a900::/41 +2408:8409:a9a0::/43 +2408:8409:a9c0::/42 +2408:8409:aa00::/42 +2408:8409:b400::/40 +2408:8409:b500::/41 +2408:8409:b5a0::/43 +2408:8409:b5c0::/42 +2408:8409:b600::/42 +2408:840c::/40 +2408:840c:200::/40 +2408:840c:d00::/40 +2408:840c:f00::/40 +2408:840c:1a00::/40 +2408:840c:1c00::/40 +2408:840c:2700::/40 +2408:840c:2900::/40 +2408:840c:3400::/40 +2408:840c:3600::/40 +2408:840c:4e00::/40 +2408:840c:5000::/40 +2408:840c:5b00::/40 +2408:840c:5d00::/40 +2408:840c:6800::/40 +2408:840c:6a00::/40 +2408:840c:7500::/40 +2408:840c:7700::/40 +2408:840c:8200::/40 +2408:840c:8400::/40 +2408:840c:8f00::/40 +2408:840c:9100::/40 +2408:840c:9c00::/40 +2408:840c:9e00::/40 +2408:840c:a900::/40 +2408:840c:ab00::/40 +2408:840c:b600::/40 +2408:840c:b800::/40 +2408:840c:c300::/40 +2408:840c:c500::/40 +2408:840c:d000::/40 +2408:840c:d200::/40 +2408:840c:dd00::/40 +2408:840c:de00::/40 +2408:840d::/42 +2408:840d:40::/43 +2408:840d:200::/42 +2408:840d:400::/42 +2408:840d:440::/43 +2408:840d:600::/42 +2408:840d:d00::/42 +2408:840d:d40::/43 +2408:840d:f00::/42 +2408:840d:1100::/42 +2408:840d:1140::/43 +2408:840d:1300::/42 +2408:840d:1a00::/42 +2408:840d:1a40::/43 +2408:840d:1c00::/42 +2408:840d:1e00::/42 +2408:840d:1e40::/43 +2408:840d:2000::/42 +2408:840d:2700::/42 +2408:840d:2740::/43 +2408:840d:2900::/42 +2408:840d:2b00::/42 +2408:840d:2b40::/43 +2408:840d:2d00::/42 +2408:840d:3400::/42 +2408:840d:3440::/43 +2408:840d:3600::/42 +2408:840d:3800::/42 +2408:840d:3840::/43 +2408:840d:3a00::/42 +2408:840d:4e00::/42 +2408:840d:4e40::/43 +2408:840d:5000::/42 +2408:840d:5200::/42 +2408:840d:5240::/43 +2408:840d:5400::/42 +2408:840d:5b00::/42 +2408:840d:5b40::/43 +2408:840d:5d00::/42 +2408:840d:5f00::/42 +2408:840d:5f40::/43 +2408:840d:6100::/42 +2408:840d:6800::/42 +2408:840d:6840::/43 +2408:840d:6a00::/42 +2408:840d:6c00::/42 +2408:840d:6c40::/43 +2408:840d:6e00::/42 +2408:840d:7500::/42 +2408:840d:7540::/43 +2408:840d:7700::/42 +2408:840d:7900::/42 +2408:840d:7940::/43 +2408:840d:7b00::/42 +2408:840d:8200::/42 +2408:840d:8240::/43 +2408:840d:8400::/42 +2408:840d:8600::/42 +2408:840d:8640::/43 +2408:840d:8800::/42 +2408:840d:8f00::/42 +2408:840d:8f40::/43 +2408:840d:9100::/42 +2408:840d:9300::/42 +2408:840d:9340::/43 +2408:840d:9500::/42 +2408:840d:9c00::/42 +2408:840d:9c40::/43 +2408:840d:9e00::/42 +2408:840d:a000::/42 +2408:840d:a040::/43 +2408:840d:a200::/42 +2408:840d:a900::/42 +2408:840d:a940::/43 +2408:840d:ab00::/42 +2408:840d:ad00::/42 +2408:840d:ad40::/43 +2408:840d:af00::/42 +2408:840d:b600::/42 +2408:840d:b640::/43 +2408:840d:b800::/42 +2408:840d:ba00::/42 +2408:840d:ba40::/43 +2408:840d:bc00::/42 +2408:840d:c300::/42 +2408:840d:c340::/43 +2408:840d:c500::/42 +2408:840d:c700::/42 +2408:840d:c740::/43 +2408:840d:c900::/42 +2408:840d:d000::/42 +2408:840d:d040::/43 +2408:840d:d200::/42 +2408:840d:d400::/42 +2408:840d:d440::/43 +2408:840d:d600::/42 +2408:840d:dd00::/42 +2408:840d:dd40::/43 +2408:840d:de00::/42 +2408:840e:dd00::/40 +2408:840e:de00::/40 +2408:840f:1e0::/46 +2408:840f:1e4::/47 +2408:8410::/30 +2408:8414::/31 +2408:8417::/32 +2408:8418::/32 +2408:841a::/31 +2408:841c::/31 +2408:841e::/32 +2408:8420::/31 +2408:8422::/32 +2408:8426::/31 +2408:842a::/31 +2408:842c::/32 +2408:842e::/32 +2408:8430::/31 +2408:8434::/30 +2408:8438::/31 +2408:843c::/30 +2408:8440::/31 +2408:8444::/30 +2408:8448::/32 +2408:844b::/32 +2408:844c::/30 +2408:8452::/31 +2408:8454::/32 +2408:8456::/31 +2408:8458::/30 +2408:845c::/31 +2408:8460::/30 +2408:8464::/31 +2408:8466::/32 +2408:8469::/32 +2408:846a::/31 +2408:846c::/30 +2408:8470::/31 +2408:8474::/30 +2408:8478::/31 +2408:847a::/32 +2408:84e1::/32 +2408:84e2::/31 +2408:84e4::/30 +2408:84e9::/32 +2408:84eb::/32 +2408:84ec::/30 +2408:84f0::/28 +2408:856c::/31 +2408:8606::/31 +2408:8608::/29 +2408:8610::/30 +2408:8614::/31 +2408:861a::/31 +2408:861c::/32 +2408:8620::/31 +2408:8624::/31 +2408:8626::/32 +2408:862a::/31 +2408:862d::/32 +2408:862e::/31 +2408:8630::/29 +2408:8638::/31 +2408:863c::/31 +2408:8640::/32 +2408:8642::/32 +2408:8644::/30 +2408:8648::/31 +2408:864c::/32 +2408:864e::/31 +2408:8650::/30 +2408:8656::/31 +2408:8658::/30 +2408:865c::/31 +2408:865f::/32 +2408:8660::/32 +2408:8662::/31 +2408:8664::/31 +2408:8666::/32 +2408:866a::/31 +2408:866c::/30 +2408:8670::/32 +2408:8674::/30 +2408:8678::/31 +2408:867a::/32 +2408:8706::/31 +2408:8708::/29 +2408:8710::/30 +2408:8719::/32 +2408:871a::/31 +2408:8720::/30 +2408:8726::/32 +2408:872b::/32 +2408:872f::/32 +2408:8730::/30 +2408:8734::/31 +2408:8736::/32 +2408:8738::/32 +2408:873c::/31 +2408:8740::/32 +2408:8742::/32 +2408:8744::/30 +2408:8748::/29 +2408:8752::/32 +2408:8756::/31 +2408:8758::/30 +2408:875c::/32 +2408:8760::/32 +2408:8762::/31 +2408:8764::/31 +2408:8766::/32 +2408:8768::/32 +2408:876a::/32 +2408:876c::/30 +2408:8770::/32 +2408:8772::/31 +2408:8774::/32 +2408:8776::/31 +2408:8778::/31 +2408:877a::/32 +2408:877c::/30 +2408:8806::/42 +2408:8806:40::/43 +2408:880c::/30 +2408:8810::/30 +2408:8814::/31 +2408:8818::/31 +2408:882c::/32 +2408:883a::/32 +2408:8844::/43 +2408:8844:40::/43 +2408:8856::/31 +2408:8858::/30 +2408:8862::/31 +2408:8864::/31 +2408:8866::/32 +2408:886e::/31 +2408:8872::/32 +2408:8878::/31 +2408:887e::/32 +2408:8906:20::/44 +2408:8906:40::/43 +2408:8907:9000::/44 +2408:890c::/31 +2408:8912::/31 +2408:8914::/30 +2408:891c::/32 +2408:8920::/32 +2408:8924::/32 +2408:892c::/32 +2408:8936::/32 +2408:893a::/32 +2408:8940::/32 +2408:8948::/32 +2408:894c::/32 +2408:894e::/32 +2408:8956::/31 +2408:8958::/30 +2408:8962::/31 +2408:8964::/31 +2408:8966::/32 +2408:896c::/32 +2408:896e::/31 +2408:8972::/32 +2408:8978::/30 +2408:897e::/32 +2408:8a00:c000::/36 +2408:8a00:d000::/37 +2408:8a00:e000::/35 +2408:8a01::/36 +2408:8a02:b110::/44 +2408:8a02:b120::/44 +2408:8a04:8000::/36 +2408:8a04:e000::/40 +2408:8a05:6000::/35 +2408:8a05:8000::/36 +2408:8a06::/47 +2408:8a06:100::/47 +2408:8a07::/40 +2408:8a21:4000::/35 +2408:8a22::/35 +2408:8a23:4000::/34 +2408:8a24:4000::/34 +2408:8a26:c000::/34 +2408:8a27:4000::/35 +2409:2000::/31 +2409:2002::/32 +2409:2003::/47 +2409:27fa::/48 +2409:27fa:f000::/48 +2409:27fb::/48 +2409:27fc::/48 +2409:27fe::/33 +2409:6100::/44 +2409:8000::/20 +240a:2000::/40 +240a:2001:100::/40 +240a:2001:1000::/36 +240a:4020:83a::/48 +240a:4020:883a::/48 +240a:4021:83a::/48 +240a:4021:883a::/48 +240a:4090:50::/48 +240a:4090:120::/48 +240a:4090:250::/48 +240a:4090:1000::/39 +240a:4090:1200::/40 +240a:4090:2010::/48 +240a:4090:2041::/48 +240a:4090:2061::/48 +240a:4090:3000::/39 +240a:4090:3200::/40 +240a:4090:5000::/39 +240a:4090:5200::/40 +240a:4090:7000::/39 +240a:4090:7200::/40 +240a:40b0:83a::/48 +240a:40b0:283a::/48 +240a:40b0:483a::/48 +240a:40b0:683a::/48 +240a:40c0:8000::/43 +240a:40c0:a000::/43 +240a:40c0:c000::/43 +240a:40c0:e000::/43 +240a:40c1::/43 +240a:40c1:2000::/43 +240a:40c1:4000::/43 +240a:40c1:6000::/43 +240a:40c1:8000::/43 +240a:40c1:a000::/43 +240a:40c1:c000::/43 +240a:40c1:e000::/43 +240a:40c2::/43 +240a:40c2:2000::/43 +240a:40c2:4000::/43 +240a:40c2:6000::/43 +240a:40c2:8000::/43 +240a:40c2:a000::/43 +240a:40c2:c000::/43 +240a:40c2:e000::/43 +240a:40c3::/43 +240a:40c3:2000::/43 +240a:40c3:4000::/43 +240a:40c3:6000::/43 +240a:40c3:8000::/43 +240a:40c3:a000::/43 +240a:40c3:c000::/43 +240a:40c3:e000::/43 +240a:40c4::/43 +240a:40c4:2000::/43 +240a:40c4:4000::/43 +240a:4280::/26 +240a:42c0::/27 +240a:42e0::/28 +240a:42f0::/29 +240a:42f8::/30 +240a:6001::/48 +240a:a000::/20 +240a:c000::/20 +240b:e001::/32 +240b:e002::/31 +240b:e004::/30 +240b:e008::/29 +240b:e010::/32 +240c:4000::/22 +240c:c000::/20 +240d:4000::/21 +240e::/20 +2605:9d80:8001::/48 +2605:9d80:8011::/48 +2605:9d80:8021::/48 +2605:9d80:8031::/48 +2605:9d80:8041::/48 +2605:9d80:8081::/48 +2605:9d80:9003::/48 +2605:9d80:9013::/48 +2605:9d80:9023::/48 +2605:9d80:9033::/48 +2605:9d80:9042::/48 +2605:9d80:9071::/48 +2605:9d80:9092::/48 +2804:1e48:9001::/48 +2804:1e48:9002::/48 +2a04:3e00:1002::/48 +2a04:f580:8010::/47 +2a04:f580:8090::/48 +2a04:f580:8210::/47 +2a04:f580:8290::/48 +2a04:f580:9010::/48 +2a04:f580:9012::/47 +2a04:f580:9020::/48 +2a04:f580:9030::/48 +2a04:f580:9040::/48 +2a04:f580:9050::/48 +2a04:f580:9060::/48 +2a04:f580:9070::/48 +2a04:f580:9080::/48 +2a04:f580:9210::/48 +2a04:f580:9212::/47 +2a04:f580:9220::/48 +2a04:f580:9230::/48 +2a04:f580:9240::/48 +2a04:f580:9250::/48 +2a04:f580:9260::/48 +2a04:f580:9270::/48 +2a04:f580:9280::/48 +2a04:f580:9290::/48 +2a05:1086::/32 +2a0a:d685:1ff::/48 +2a0a:d685:300::/40 +2a0b:4340:97::/48 +2a0d:c7c7:400::/38 +2a0e:aa06:49f::/48 +2a0e:b107:740::/44 +2a0f:1cc5:1600::/44 +2a0f:1cc5:4700::/40 +2a0f:9400:6110::/48 +2a12:cb41:600::/44 +2a12:cb41:1200::/44 +2a12:cb41:1300::/44 +2a12:cb41:13f0::/44 +2a13:1801:180::/43 +2a13:a5c3:f000::/40 +2a14:7580:d10::/44 +2a14:7580:d20::/46 +2a14:7583:efe7::/48 +2c0f:f7a8:8011::/48 +2c0f:f7a8:8050::/48 +2c0f:f7a8:805f::/48 +2c0f:f7a8:8150::/48 +2c0f:f7a8:815f::/48 +2c0f:f7a8:8211::/48 +2c0f:f7a8:9020::/48 +2c0f:f7a8:9041::/48 +2c0f:f7a8:9220::/48 diff --git a/luci-app-ssr-plus/root/usr/bin/ssr-rules b/luci-app-ssr-plus/root/usr/bin/ssr-rules index d123a5e6a..fcc85da48 100755 --- a/luci-app-ssr-plus/root/usr/bin/ssr-rules +++ b/luci-app-ssr-plus/root/usr/bin/ssr-rules @@ -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 diff --git a/luci-app-ssr-plus/root/usr/share/shadowsocksr/chinaipset.sh b/luci-app-ssr-plus/root/usr/share/shadowsocksr/chinaipset.sh index 48164d1cb..bfb410032 100755 --- a/luci-app-ssr-plus/root/usr/share/shadowsocksr/chinaipset.sh +++ b/luci-app-ssr-plus/root/usr/share/shadowsocksr/chinaipset.sh @@ -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}" diff --git a/luci-app-ssr-plus/root/usr/share/shadowsocksr/clash_yaml.lua b/luci-app-ssr-plus/root/usr/share/shadowsocksr/clash_yaml.lua index 8bc89e0bc..beda6d74c 100644 --- a/luci-app-ssr-plus/root/usr/share/shadowsocksr/clash_yaml.lua +++ b/luci-app-ssr-plus/root/usr/share/shadowsocksr/clash_yaml.lua @@ -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", diff --git a/luci-app-ssr-plus/root/usr/share/shadowsocksr/gen_config.lua b/luci-app-ssr-plus/root/usr/share/shadowsocksr/gen_config.lua index 868fa1e90..9284efc95 100755 --- a/luci-app-ssr-plus/root/usr/share/shadowsocksr/gen_config.lua +++ b/luci-app-ssr-plus/root/usr/share/shadowsocksr/gen_config.lua @@ -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 = { diff --git a/luci-app-ssr-plus/root/usr/share/shadowsocksr/gfw2ipset.sh b/luci-app-ssr-plus/root/usr/share/shadowsocksr/gfw2ipset.sh index 7528c008e..187b55236 100755 --- a/luci-app-ssr-plus/root/usr/share/shadowsocksr/gfw2ipset.sh +++ b/luci-app-ssr-plus/root/usr/share/shadowsocksr/gfw2ipset.sh @@ -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 } diff --git a/luci-app-ssr-plus/root/usr/share/shadowsocksr/ipv6.sh b/luci-app-ssr-plus/root/usr/share/shadowsocksr/ipv6.sh new file mode 100755 index 000000000..8460b3ec9 --- /dev/null +++ b/luci-app-ssr-plus/root/usr/share/shadowsocksr/ipv6.sh @@ -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 +} diff --git a/luci-app-ssr-plus/root/usr/share/shadowsocksr/shadowsocksr.config b/luci-app-ssr-plus/root/usr/share/shadowsocksr/shadowsocksr.config index 819ce7641..0810b8850 100644 --- a/luci-app-ssr-plus/root/usr/share/shadowsocksr/shadowsocksr.config +++ b/luci-app-ssr-plus/root/usr/share/shadowsocksr/shadowsocksr.config @@ -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' diff --git a/luci-app-ssr-plus/root/usr/share/shadowsocksr/update.lua b/luci-app-ssr-plus/root/usr/share/shadowsocksr/update.lua index 9526b6886..33ce23cec 100755 --- a/luci-app-ssr-plus/root/usr/share/shadowsocksr/update.lua +++ b/luci-app-ssr-plus/root/usr/share/shadowsocksr/update.lua @@ -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 diff --git a/luci-app-ssr-plus/tests/ipv6-cbi.js b/luci-app-ssr-plus/tests/ipv6-cbi.js new file mode 100644 index 000000000..966b863d7 --- /dev/null +++ b/luci-app-ssr-plus/tests/ipv6-cbi.js @@ -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'); diff --git a/luci-app-ssr-plus/tests/ipv6-dns.py b/luci-app-ssr-plus/tests/ipv6-dns.py new file mode 100644 index 000000000..3141fe266 --- /dev/null +++ b/luci-app-ssr-plus/tests/ipv6-dns.py @@ -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') diff --git a/luci-app-ssr-plus/tests/ipv6-nft.sh b/luci-app-ssr-plus/tests/ipv6-nft.sh new file mode 100755 index 000000000..bb2da5711 --- /dev/null +++ b/luci-app-ssr-plus/tests/ipv6-nft.sh @@ -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" diff --git a/luci-mod-network/htdocs/luci-static/resources/view/network/wireless.js b/luci-mod-network/htdocs/luci-static/resources/view/network/wireless.js index cb5208bb8..1e6389a66 100644 --- a/luci-mod-network/htdocs/luci-static/resources/view/network/wireless.js +++ b/luci-mod-network/htdocs/luci-static/resources/view/network/wireless.js @@ -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');