From f920eec7cfe204348a57bbc6dd03fb013a0f729c Mon Sep 17 00:00:00 2001 From: "github-action[bot]" Date: Sun, 7 Dec 2025 19:36:55 +0100 Subject: [PATCH] Update On Sun Dec 7 19:36:54 CET 2025 --- .github/update.log | 1 + clash-nyanpasu/manifest/version.json | 6 +- filebrowser/CHANGELOG.md | 15 + .../src/components/files/CsvViewer.vue | 12 +- .../src/components/files/ListingItem.vue | 11 +- filebrowser/frontend/src/i18n/en.json | 18 +- lede/include/target.mk | 3 + lede/scripts/target-metadata.pl | 1 + lede/target/Config.in | 3 + mieru/test/deploy/singbox/test_client.sh | 48 +- openwrt-packages/ddns-go/Makefile | 4 +- openwrt-packages/filebrowser/Makefile | 4 +- .../cbi/passwall/client/socks_config.lua | 12 +- .../model/cbi/passwall/client/type/ray.lua | 14 +- .../cbi/passwall/client/type/sing-box.lua | 14 +- .../view/passwall/cbi/nodes_multiselect.htm | 47 +- .../view/passwall/node_list/link_add_node.htm | 2 +- .../view/passwall/node_list/node_list.htm | 5 - shadowsocks-rust/Cargo.lock | 99 +- shadowsocks-rust/Cargo.toml | 2 +- .../crates/shadowsocks-service/Cargo.toml | 2 +- .../crates/shadowsocks-service/src/config.rs | 6 +- .../src/local/socks/config.rs | 2 +- shadowsocks-rust/src/config.rs | 4 +- sing-box/adapter/network.go | 4 +- sing-box/adapter/router.go | 1 - sing-box/box.go | 2 +- sing-box/common/dialer/default.go | 2 +- sing-box/common/listener/listener_tcp.go | 2 +- sing-box/common/settings/wifi.go | 9 + sing-box/common/settings/wifi_linux.go | 46 + .../common/settings/wifi_linux_connman.go | 166 +++ sing-box/common/settings/wifi_linux_iwd.go | 188 +++ sing-box/common/settings/wifi_linux_nm.go | 163 +++ sing-box/common/settings/wifi_linux_wpa.go | 225 ++++ sing-box/common/settings/wifi_stub.go | 27 + sing-box/common/settings/wifi_windows.go | 144 +++ sing-box/docs/changelog.md | 2 +- sing-box/docs/configuration/dns/rule.md | 4 +- sing-box/docs/configuration/dns/rule.zh.md | 4 +- sing-box/docs/configuration/route/rule.md | 4 +- sing-box/docs/configuration/route/rule.zh.md | 4 +- sing-box/docs/configuration/shared/dial.md | 10 +- sing-box/docs/configuration/shared/dial.zh.md | 10 +- sing-box/docs/configuration/shared/listen.md | 10 +- .../docs/configuration/shared/listen.zh.md | 10 +- sing-box/experimental/libbox/config.go | 4 + .../experimental/libbox/platform/interface.go | 1 + sing-box/experimental/libbox/service.go | 6 +- sing-box/go.mod | 2 +- sing-box/go.sum | 10 +- sing-box/option/inbound.go | 1 + sing-box/option/outbound.go | 1 + sing-box/route/network.go | 76 +- sing-box/route/router.go | 10 +- small/gn/Makefile | 6 +- small/gn/src/out/last_commit_position.h | 4 +- .../luci-static/resources/view/fchomo/node.js | 30 +- .../resources/view/fchomo/ruleset.js | 22 +- small/luci-app-fchomo/po/templates/fchomo.pot | 554 ++++----- small/luci-app-fchomo/po/zh_Hans/fchomo.po | 569 ++++----- small/luci-app-fchomo/po/zh_Hant/fchomo.po | 569 ++++----- .../model/cbi/passwall/client/node_config.lua | 4 +- .../cbi/passwall/client/node_subscribe.lua | 9 +- .../passwall/client/node_subscribe_config.lua | 9 +- .../cbi/passwall/client/socks_config.lua | 48 +- .../model/cbi/passwall/client/type/ray.lua | 36 +- .../cbi/passwall/client/type/sing-box.lua | 36 +- .../luci-app-passwall/luasrc/passwall/api.lua | 8 +- .../view/passwall/cbi/nodes_multiselect.htm | 284 +++++ .../view/passwall/node_list/link_add_node.htm | 2 +- .../view/passwall/node_list/node_list.htm | 5 - small/luci-app-passwall/po/zh-cn/passwall.po | 6 + .../model/cbi/shadowsocksr/advanced.lua | 4 +- .../luasrc/model/cbi/shadowsocksr/client.lua | 4 +- .../luasrc/model/cbi/shadowsocksr/status.lua | 4 + .../luci-app-ssr-plus/po/zh_Hans/ssr-plus.po | 4 +- .../root/etc/init.d/shadowsocksr | 50 +- .../luci-app-ssr-plus/root/usr/bin/ssr-rules | 1041 ++++++++++++++--- .../root/usr/share/shadowsocksr/update.lua | 4 +- small/v2ray-geodata/Makefile | 4 +- v2rayn/v2rayN/Directory.Build.props | 2 +- v2rayn/v2rayN/Directory.Packages.props | 6 +- v2rayn/v2rayN/ServiceLib/Global.cs | 1 - .../Manager/ActionPrecheckManager.cs | 2 + .../Singbox/SingboxOutboundService.cs | 2 +- .../Common/AppBuilderExtension.cs | 8 +- .../Views/AddGroupServerWindow.axaml | 5 +- .../Views/OptionSettingWindow.axaml | 3 +- .../Views/OptionSettingWindow.axaml.cs | 2 +- .../v2rayN/Views/AddGroupServerWindow.xaml | 7 +- .../.github/workflows/tidy.yml | 10 + v2rayng/AndroidLibXrayLite/go.mod | 2 +- v2rayng/V2rayNG/app/build.gradle.kts | 4 +- yt-dlp/yt_dlp/extractor/_extractors.py | 1 + yt-dlp/yt_dlp/extractor/alibaba.py | 42 + yt-dlp/yt_dlp/extractor/sportdeutschland.py | 36 +- yt-dlp/yt_dlp/extractor/xhamster.py | 51 +- 98 files changed, 3582 insertions(+), 1401 deletions(-) create mode 100644 sing-box/common/settings/wifi.go create mode 100644 sing-box/common/settings/wifi_linux.go create mode 100644 sing-box/common/settings/wifi_linux_connman.go create mode 100644 sing-box/common/settings/wifi_linux_iwd.go create mode 100644 sing-box/common/settings/wifi_linux_nm.go create mode 100644 sing-box/common/settings/wifi_linux_wpa.go create mode 100644 sing-box/common/settings/wifi_stub.go create mode 100644 sing-box/common/settings/wifi_windows.go create mode 100644 small/luci-app-passwall/luasrc/view/passwall/cbi/nodes_multiselect.htm create mode 100644 yt-dlp/yt_dlp/extractor/alibaba.py diff --git a/.github/update.log b/.github/update.log index 4d5e11ded2..3b79ddc308 100644 --- a/.github/update.log +++ b/.github/update.log @@ -1204,3 +1204,4 @@ Update On Wed Dec 3 19:42:45 CET 2025 Update On Thu Dec 4 19:44:01 CET 2025 Update On Fri Dec 5 19:41:34 CET 2025 Update On Sat Dec 6 19:36:39 CET 2025 +Update On Sun Dec 7 19:36:46 CET 2025 diff --git a/clash-nyanpasu/manifest/version.json b/clash-nyanpasu/manifest/version.json index 44a3134202..166cd8379c 100644 --- a/clash-nyanpasu/manifest/version.json +++ b/clash-nyanpasu/manifest/version.json @@ -2,10 +2,10 @@ "manifest_version": 1, "latest": { "mihomo": "v1.19.17", - "mihomo_alpha": "alpha-f44aa22", + "mihomo_alpha": "alpha-6b40072", "clash_rs": "v0.9.2", "clash_premium": "2023-09-05-gdcc8d87", - "clash_rs_alpha": "0.9.2-alpha+sha.81f5ac5" + "clash_rs_alpha": "0.9.2-alpha+sha.53b277f" }, "arch_template": { "mihomo": { @@ -69,5 +69,5 @@ "linux-armv7hf": "clash-armv7-unknown-linux-gnueabihf" } }, - "updated_at": "2025-12-05T22:21:23.531Z" + "updated_at": "2025-12-06T22:21:16.955Z" } diff --git a/filebrowser/CHANGELOG.md b/filebrowser/CHANGELOG.md index 05671b1d3c..58f6ffd50f 100644 --- a/filebrowser/CHANGELOG.md +++ b/filebrowser/CHANGELOG.md @@ -2,6 +2,21 @@ All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines. +## [2.51.2](https://github.com/filebrowser/filebrowser/compare/v2.51.1...v2.51.2) (2025-12-07) + + +### Bug Fixes + +* **frontend:** add missing i18n strings ([c171599](https://github.com/filebrowser/filebrowser/commit/c1715992bda46517f801c1aa496df8a3b42a4e4d)) + +## [2.51.1](https://github.com/filebrowser/filebrowser/compare/v2.51.0...v2.51.1) (2025-12-07) + + +### Bug Fixes + +* **frontend:** csv viewer i18n strings ([4cbb4b7](https://github.com/filebrowser/filebrowser/commit/4cbb4b73af816104475f15c1d996640b56203602)) +* prevent the right-click from selecting multiple items when the "single-click" option is active ([#5608](https://github.com/filebrowser/filebrowser/issues/5608)) ([152f830](https://github.com/filebrowser/filebrowser/commit/152f8302f7cda21bde37692b175c22c124233f45)) + ## [2.51.0](https://github.com/filebrowser/filebrowser/compare/v2.50.0...v2.51.0) (2025-12-06) diff --git a/filebrowser/frontend/src/components/files/CsvViewer.vue b/filebrowser/frontend/src/components/files/CsvViewer.vue index 4f66fff075..b926d3901b 100644 --- a/filebrowser/frontend/src/components/files/CsvViewer.vue +++ b/filebrowser/frontend/src/components/files/CsvViewer.vue @@ -28,23 +28,25 @@ diff --git a/small/luci-app-passwall/luasrc/view/passwall/node_list/node_list.htm b/small/luci-app-passwall/luasrc/view/passwall/node_list/node_list.htm index 707452e8a0..84a04810e5 100644 --- a/small/luci-app-passwall/luasrc/view/passwall/node_list/node_list.htm +++ b/small/luci-app-passwall/luasrc/view/passwall/node_list/node_list.htm @@ -160,11 +160,6 @@ table td, .table .td { background-color: rgba(131, 191, 255, 0.7) !important; box-shadow: 0 4px 6px rgba(0,0,0,0.1); } - -/* hide save button */ -.cbi-page-actions { - display: none !important; -} <% if api.is_js_luci() then -%> diff --git a/small/luci-app-passwall/po/zh-cn/passwall.po b/small/luci-app-passwall/po/zh-cn/passwall.po index f6ca3935d1..04d5303a6f 100644 --- a/small/luci-app-passwall/po/zh-cn/passwall.po +++ b/small/luci-app-passwall/po/zh-cn/passwall.po @@ -388,6 +388,9 @@ msgstr "置顶" msgid "Select" msgstr "选择" +msgid "Selected:" +msgstr "已选:" + msgid "DeSelect" msgstr "反选" @@ -2016,3 +2019,6 @@ msgstr "调整节点分组" msgid "Currently using %s node" msgstr "当前使用的 %s 节点" + +msgid "Search nodes..." +msgstr "搜索节点…" diff --git a/small/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua b/small/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua index 8134860f8a..b57d437066 100644 --- a/small/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua +++ b/small/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua @@ -138,7 +138,7 @@ o:depends("shunt_dns_mode", "2") o.description = translate("Custom DNS Server format as IP:PORT (default: 8.8.4.4:53)") o.datatype = "ip4addrport" -o = s:option(ListValue, "shunt_mosdns_dnsserver", translate("Anti-pollution DNS Server")) +o = s:option(Value, "shunt_mosdns_dnsserver", translate("Anti-pollution DNS Server")) o:value("tcp://8.8.4.4:53,tcp://8.8.8.8:53", translate("Google Public DNS")) o:value("tcp://208.67.222.222:53,tcp://208.67.220.220:53", translate("OpenDNS")) o:value("tcp://209.244.0.3:53,tcp://209.244.0.4:53", translate("Level 3 Public DNS-1 (209.244.0.3-4)")) @@ -146,7 +146,7 @@ o:value("tcp://4.2.2.1:53,tcp://4.2.2.2:53", translate("Level 3 Public DNS-2 (4. o:value("tcp://4.2.2.3:53,tcp://4.2.2.4:53", translate("Level 3 Public DNS-3 (4.2.2.3-4)")) o:value("tcp://1.1.1.1:53,tcp://1.0.0.1:53", translate("Cloudflare DNS")) o:depends("shunt_dns_mode", "3") -o.description = translate("Custom DNS Server for MosDNS") +o.description = translate("Custom DNS Server format as tcp://IP:PORT or tls://DOMAIN:PORT (tcp://8.8.8.8 or tls://dns.google:853)") o = s:option(Flag, "shunt_mosdns_ipv6", translate("Disable IPv6 In MosDNS Query Mode (Shunt Mode)")) o:depends("shunt_dns_mode", "3") diff --git a/small/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua b/small/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua index 56f7dc16d9..17630423fe 100644 --- a/small/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua +++ b/small/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua @@ -161,7 +161,7 @@ o.description = translate("Custom DNS Server format as IP:PORT (default: 8.8.4.4 o.datatype = "ip4addrport" o.default = "8.8.4.4:53" -o = s:option(ListValue, "tunnel_forward_mosdns", translate("Anti-pollution DNS Server")) +o = s:option(Value, "tunnel_forward_mosdns", translate("Anti-pollution DNS Server")) o:value("tcp://8.8.4.4:53,tcp://8.8.8.8:53", translate("Google Public DNS")) o:value("tcp://208.67.222.222:53,tcp://208.67.220.220:53", translate("OpenDNS")) o:value("tcp://209.244.0.3:53,tcp://209.244.0.4:53", translate("Level 3 Public DNS-1 (209.244.0.3-4)")) @@ -169,7 +169,7 @@ o:value("tcp://4.2.2.1:53,tcp://4.2.2.2:53", translate("Level 3 Public DNS-2 (4. o:value("tcp://4.2.2.3:53,tcp://4.2.2.4:53", translate("Level 3 Public DNS-3 (4.2.2.3-4)")) o:value("tcp://1.1.1.1:53,tcp://1.0.0.1:53", translate("Cloudflare DNS")) o:depends("pdnsd_enable", "4") -o.description = translate("Custom DNS Server for MosDNS") +o.description = translate("Custom DNS Server format as tcp://IP:PORT or tls://DOMAIN:PORT (tcp://8.8.8.8 or tls://dns.google:853)") o = s:option(Flag, "mosdns_ipv6", translate("Disable IPv6 in MOSDNS query mode")) o:depends("pdnsd_enable", "4") diff --git a/small/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua b/small/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua index 9f0eff11e8..4f52aab19a 100644 --- a/small/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua +++ b/small/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/status.lua @@ -73,6 +73,10 @@ if Process_list:find("tcp.udp.ssr.retcp") then reudp_run = 1 end +if Process_list:find("nft.ssr.retcp") then + redir_run = 1 +end + if Process_list:find("local.ssr.retcp") then redir_run = 1 sock5_run = 1 diff --git a/small/luci-app-ssr-plus/po/zh_Hans/ssr-plus.po b/small/luci-app-ssr-plus/po/zh_Hans/ssr-plus.po index a48a1d51cb..e56d8391e9 100644 --- a/small/luci-app-ssr-plus/po/zh_Hans/ssr-plus.po +++ b/small/luci-app-ssr-plus/po/zh_Hans/ssr-plus.po @@ -416,8 +416,8 @@ msgstr "" #: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:149 #: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client.lua:172 -msgid "Custom DNS Server for MosDNS" -msgstr "MosDNS 自定义 DNS 服务器" +msgid "Custom DNS Server format as tcp://IP:PORT or tls://DOMAIN:PORT (tcp://8.8.8.8 or tls://dns.google:853)" +msgstr "格式为tcp://IP:Port或tls://域名:Port (tcp://8.8.8.8或tls://dns.google:853)" #: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:138 #: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:220 diff --git a/small/luci-app-ssr-plus/root/etc/init.d/shadowsocksr b/small/luci-app-ssr-plus/root/etc/init.d/shadowsocksr index 3acb750cc8..95e03a6b8a 100755 --- a/small/luci-app-ssr-plus/root/etc/init.d/shadowsocksr +++ b/small/luci-app-ssr-plus/root/etc/init.d/shadowsocksr @@ -211,8 +211,10 @@ start_dns() { local run_mode="$(uci_get_by_type global run_mode)" if [ "$ssrplus_dns" != "0" ]; then - if [ -n "$dnsserver" ]; then - add_dns_into_ipset $run_mode $dnsserver + if command -v iptables-legacy >/dev/null 2>&1; then + if [ -n "$dnsserver" ]; then + add_dns_into_ipset $run_mode $dnsserver + fi fi case "$ssrplus_dns" in 1) @@ -236,12 +238,14 @@ start_dns() { output=$(for i in $(echo $mosdns_dnsserver | sed "s/,/ /g"); do dnsserver=${i%:*} dnsserver=${i##*/} - add_dns_into_ipset $run_mode $dnsserver + if command -v iptables-legacy >/dev/null 2>&1; then + add_dns_into_ipset $run_mode $dnsserver + fi echo " - addr: $i" echo " enable_pipeline: true" done) - awk -v line=14 -v text="$output" 'NR == line+1 {print text} 1' /etc/ssrplus/mosdns-config.yaml | sed "s/DNS_PORT/$dns_port/g" > $TMP_PATH/mosdns-config.yaml + awk -v line=14 -v text="$output" 'NR == line+1 {print text} 1' /etc/ssrplus/mosdns-config.yaml | sed "s/DNS_PORT/$dns_port/g" | sed "s/\(concurrent:\).*/\1 $(echo "$mosdns_dnsserver" | sed 's/,/ /g' | wc -w)/g"> $TMP_PATH/mosdns-config.yaml if [ "$mosdns_ipv6" == "0" ]; then sed -i "s/DNS_MODE/main_sequence_with_IPv6/g" $TMP_PATH/mosdns-config.yaml else @@ -648,7 +652,7 @@ shunt_dns_command() { echo " socks5: \"127.0.0.1:$tmp_port\"" echo " enable_pipeline: true" done) - awk -v line=14 -v text="$output" 'NR == line+1 {print text} 1' /etc/ssrplus/mosdns-config.yaml | sed "s/DNS_PORT/$tmp_shunt_dns_port/g" > $TMP_PATH/mosdns-config-shunt.yaml + awk -v line=14 -v text="$output" 'NR == line+1 {print text} 1' /etc/ssrplus/mosdns-config.yaml | sed "s/DNS_PORT/$tmp_shunt_dns_port/g" | sed "s/\(concurrent:\).*/\1 $(echo "$mosdns_dnsserver" | sed 's/,/ /g' | wc -w)/g" > $TMP_PATH/mosdns-config-shunt.yaml if [ "$shunt_mosdns_ipv6" == "0" ]; then sed -i "s/DNS_MODE/main_sequence_with_IPv6/g" $TMP_PATH/mosdns-config-shunt.yaml @@ -1172,7 +1176,17 @@ load_config() { tcp_config_file=$TMP_PATH/tcp-only-ssr-retcp.json case "$UDP_RELAY_SERVER" in nil) - mode="tcp" + if command -v nft >/dev/null 2>&1; then + # nftables / fw4 + mode="tcp,udp" + ARG_UDP="" + udp_config_file="" + UDP_RELAY_SERVER="nil" + tcp_config_file=$TMP_PATH/nft-ssr-retcp.json + else + # iptables / fw3 + mode="tcp" + fi ;; $GLOBAL_SERVER | same) mode="tcp,udp" @@ -1185,7 +1199,13 @@ load_config() { udp_config_file=$TMP_PATH/udp-only-ssr-reudp.json ARG_UDP="-U" start_udp - mode="tcp" + if command -v nft >/dev/null 2>&1; then + # nftables / fw4 + mode="tcp,udp" + else + # iptables / fw3 + mode="tcp" + fi ;; esac case "$LOCAL_SERVER" in @@ -1419,6 +1439,11 @@ start_rules() { 2) echo "-O" ;; esac } + if command -v nft >/dev/null 2>&1; then + ARG_A="-A" + else + ARG_A="" + fi /usr/share/shadowsocksr/gfw2ipset.sh /usr/bin/ssr-rules \ -s "$server" \ @@ -1438,7 +1463,8 @@ start_rules() { -N "$shunt_ip" \ -M "$(uci_get_by_type global netflix_proxy 0)" \ -I "/etc/ssrplus/netflixip.list" \ - $(get_arg_out) $(gfwmode) $ARG_UDP + $(get_arg_out) $(gfwmode) $ARG_UDP $ARG_A + return $? } @@ -1486,6 +1512,13 @@ boot() { stop() { unlock set_lock + if command -v nft >/dev/null 2>&1; then + /usr/bin/ssr-rules -K + #local CLEANUP_PERSISTENCE="$(uci_get_by_type global global_server nil)" + #if [ "$CLEANUP_PERSISTENCE" == "nil" ]; then + # /usr/bin/ssr-rules -X + #fi + fi /usr/bin/ssr-rules -f local srulecount=0 if command -v nft >/dev/null 2>&1; then @@ -1527,6 +1560,7 @@ stop() { killall -q -9 kcptun-client fi $PS -w | grep -v "grep" | grep ssr-monitor | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1 & + $PS -w | grep -v "grep" | grep ssr-rules | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1 & $PS -w | grep -v "grep" | grep "sleep 0000" | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1 & ( \ # Graceful kill first, so programs have the chance to stop its subprocesses diff --git a/small/luci-app-ssr-plus/root/usr/bin/ssr-rules b/small/luci-app-ssr-plus/root/usr/bin/ssr-rules index 8fb813a447..fba38d8731 100755 --- a/small/luci-app-ssr-plus/root/usr/bin/ssr-rules +++ b/small/luci-app-ssr-plus/root/usr/bin/ssr-rules @@ -28,6 +28,24 @@ detect_firewall TAG="_SS_SPEC_RULE_" # comment tag +# 这些变量将在后续的 getopts 参数解析中被赋值 +ENABLE_AUTO_UPDATE=0 +STOP_AUTO_UPDATE=0 +FORCE_UPDATE=0 +CHECK_STATUS=0 +RESTORE_RULES=0 +FLUSH_RULES=0 +CLEANUP_PERSISTENCE=0 + +if [ "$USE_NFT" = "1" ]; then + # NFTables persistence directory + NFTABLES_RULES_DIR="/usr/share/nftables.d/ruleset-post" + NFTABLES_RULES_FILE="$NFTABLES_RULES_DIR/99-shadowsocksr.nft" + # Auto-update configuration + AUTO_UPDATE_INTERVAL=300 # 自动更新检查间隔(秒),0表示禁用自动更新 +fi + +# 修改 usage 函数 usage() { cat <<-EOF Usage: ssr-rules [options] @@ -63,6 +81,15 @@ usage() { -r router mode -c oversea mode -z all mode + + # 新增持久化管理选项 (使用不同的字母避免冲突) + -A enable auto-update daemon + -K stop auto-update daemon + -P force update persistence + -C check rules status + -R restore rules from persistence file + -X cleanup persistence files on stop + -h show this help message and exit EOF exit $1 @@ -73,6 +100,40 @@ loger() { logger -st ssr-rules[$$] -p$1 $2 } +# 清理持久化和运行模块文件等 +cleanup_persistence_files() { + if [ "$USE_NFT" != "1" ]; then + return 0 + fi + + # 删除持久化规则文件 + if [ -f "$NFTABLES_RULES_FILE" ]; then + rm -f "$NFTABLES_RULES_FILE" 2>/dev/null + loger 5 "Removed persistence file: $NFTABLES_RULES_FILE" + fi + + # 删除运行模块文件 + if [ -f "/tmp/.ssr_run_mode" ]; then + rm -f "/tmp/.ssr_run_mode" 2>/dev/null + loger 5 "Removed run mode file: /tmp/.ssr_run_mode" + fi + + # 删除 TPROXY 文件 + if [ -f "/tmp/.last_tproxy" ]; then + rm -f "/tmp/.last_tproxy" 2>/dev/null + loger 5 "Removed run mode file: /tmp/.last_tproxy" + fi + + # 删除 PROXY_PORTS 文件 + if [ -f "/tmp/.last_proxy_ports" ]; then + rm -f "/tmp/.last_proxy_ports" 2>/dev/null + loger 5 "Removed run mode file: /tmp/.last_proxy_ports" + fi + + loger 5 "Persistence cleanup completed" + return 0 +} + flush_r() { if [ "$USE_NFT" = "1" ]; then flush_nftables @@ -136,6 +197,13 @@ flush_nftables() { # 重置防火墙 include 文件 [ -n "$FWI" ] && echo '#!/bin/sh' >"$FWI" + # 清理持久化和运行模块文件 + if [ "$CLEANUP_PERSISTENCE" = "1" ]; then + cleanup_persistence_files + fi + + loger 6 "Memory rules flushed successfully" + return 0 } @@ -182,7 +250,7 @@ ipset_nft() { fi # Create necessary collections - for setname in china gmlan fplan bplan whitelist blacklist netflix; do + for setname in china gmlan fplan bplan whitelist blacklist netflix music; do if ! $NFT list set inet ss_spec $setname >/dev/null 2>&1; then $NFT add set inet ss_spec $setname '{ type ipv4_addr; flags interval; auto-merge; }' 2>/dev/null else @@ -190,7 +258,7 @@ ipset_nft() { fi done - # 批量导入中国IP列表 + # Bulk import china ip list safely (avoid huge single element limitation) if [ -f "${china_ip:=/etc/ssrplus/china_ssr.txt}" ]; then $NFT add element inet ss_spec china "{ $(tr '\n' ',' < "${china_ip}" | sed 's/,$//') }" 2>/dev/null fi @@ -212,109 +280,111 @@ ipset_nft() { [ -n "$ip" ] && $NFT add element inet ss_spec blacklist "{ $ip }" 2>/dev/null done - # Create main chain for WAN access control - if ! $NFT list chain inet ss_spec ss_spec_wan_ac >/dev/null 2>&1; then - $NFT add chain inet ss_spec ss_spec_wan_ac 2>/dev/null - fi - $NFT flush chain inet ss_spec ss_spec_wan_ac 2>/dev/null - - # Create forward chain with better error handling - if ! $NFT list chain inet ss_spec ss_spec_wan_fw >/dev/null 2>&1; then - $NFT add chain inet ss_spec ss_spec_wan_fw 2>/dev/null || { - loger 3 "Failed to create forward chain" - return 1 - } - fi - # Clear existing rules - $NFT flush chain inet ss_spec ss_spec_wan_fw 2>/dev/null - - EXT_ARGS="" - if [ -n "$PROXY_PORTS" ]; then - PORTS_ARGS=$(echo "$PROXY_PORTS" | sed 's/-m multiport --dports //') - if [ -n "$PORTS_ARGS" ]; then - EXT_ARGS="th dport { $PORTS_ARGS }" + # Create main chains for WAN access control + for chain in ss_spec_wan_fw_tcp ss_spec_wan_fw_udp ss_spec_wan_ac_tcp ss_spec_wan_ac_udp; do + if ! $NFT list chain inet ss_spec $chain >/dev/null 2>&1; then + $NFT add chain inet ss_spec $chain fi - fi + $NFT flush chain inet ss_spec $chain + done # Add basic rules - # ========== 按照正确顺序添加规则 ========== + # BASIC RULES (exceptions first) — TCP + $NFT add rule inet ss_spec ss_spec_wan_ac_tcp meta l4proto tcp tcp dport 53 ip daddr 127.0.0.0/8 return + [ -n "$server" ] && $NFT add rule inet ss_spec ss_spec_wan_ac_tcp meta l4proto tcp tcp dport != 53 ip daddr "$server" return - # 1. 基础例外规则(最高优先级) - $NFT add rule inet ss_spec ss_spec_wan_ac tcp dport 53 ip daddr 127.0.0.0/8 return - [ -n "$server" ] && $NFT add rule inet ss_spec ss_spec_wan_ac tcp dport != 53 ip daddr "$server" return + # Access control: blacklist -> whitelist -> fplan/bplan — TCP + $NFT add rule inet ss_spec ss_spec_wan_ac_tcp ip daddr @blacklist jump ss_spec_wan_fw_tcp + $NFT add rule inet ss_spec ss_spec_wan_ac_tcp ip daddr @whitelist return + $NFT add rule inet ss_spec ss_spec_wan_ac_tcp ip saddr @fplan jump ss_spec_wan_fw_tcp + $NFT add rule inet ss_spec ss_spec_wan_ac_tcp ip saddr @bplan return - # 2. 强制访问控制 - $NFT add rule inet ss_spec ss_spec_wan_ac ip daddr @blacklist jump ss_spec_wan_fw - $NFT add rule inet ss_spec ss_spec_wan_ac ip daddr @whitelist return - $NFT add rule inet ss_spec ss_spec_wan_ac ip saddr @fplan jump ss_spec_wan_fw - $NFT add rule inet ss_spec ss_spec_wan_ac ip saddr @bplan return + # BASIC RULES (exceptions first) — UDP + $NFT add rule inet ss_spec ss_spec_wan_ac_udp meta l4proto udp udp dport 53 ip daddr 127.0.0.0/8 return + [ -n "$server" ] && $NFT add rule inet ss_spec ss_spec_wan_ac_udp meta l4proto udp udp dport != 53 ip daddr "$server" return + + # Access control: blacklist -> whitelist -> fplan/bplan — UDP + $NFT add rule inet ss_spec ss_spec_wan_ac_udp ip daddr @blacklist jump ss_spec_wan_fw_udp + $NFT add rule inet ss_spec ss_spec_wan_ac_udp ip daddr @whitelist return + $NFT add rule inet ss_spec ss_spec_wan_ac_udp ip saddr @fplan jump ss_spec_wan_fw_udp + $NFT add rule inet ss_spec ss_spec_wan_ac_udp ip saddr @bplan return - # 3. 特殊功能规则 # Music unlocking support if $NFT list set inet ss_spec music >/dev/null 2>&1; then - $NFT add rule inet ss_spec ss_spec_wan_ac ip daddr @music return + $NFT add rule inet ss_spec ss_spec_wan_ac_tcp meta l4proto tcp ip daddr @music return + $NFT add rule inet ss_spec ss_spec_wan_ac_udp meta l4proto udp ip daddr @music return fi # Shunt/Netflix rules - if [ "$SHUNT_PORT" != "0" ] && [ -f "$SHUNT_LIST" ]; then + if [ -f "$SHUNT_LIST" ]; then for ip in $(cat "$SHUNT_LIST" 2>/dev/null); do [ -n "$ip" ] && $NFT add element inet ss_spec netflix "{ $ip }" 2>/dev/null done - case "$SHUNT_PORT" in - 1) - $NFT add rule inet ss_spec ss_spec_wan_ac meta l4proto tcp $EXT_ARGS ip daddr @netflix counter redirect to :$local_port - ;; - *) - $NFT add rule inet ss_spec ss_spec_wan_ac meta l4proto tcp $EXT_ARGS ip daddr @netflix counter redirect to :$SHUNT_PORT - if [ "$SHUNT_PROXY" = "1" ]; then - $NFT add rule inet ss_spec ss_spec_wan_ac meta l4proto tcp $EXT_ARGS ip daddr $SHUNT_IP counter redirect to :$local_port - else - [ -n "$SHUNT_IP" ] && $NFT add element inet ss_spec whitelist "{ $SHUNT_IP }" 2>/dev/null - fi - ;; - esac fi - # 4. 模式特定规则 # Set up mode-specific rules case "$RUNMODE" in router) - if ! $NFT list set inet ss_spec ss_spec_wan_ac >/dev/null 2>&1; then - $NFT add set inet ss_spec ss_spec_wan_ac '{ type ipv4_addr; flags interval; auto-merge; }' + if ! $NFT list set inet ss_spec ss_spec_wan_ac_tcp >/dev/null 2>&1; then + $NFT add set inet ss_spec ss_spec_wan_ac_tcp '{ type ipv4_addr; flags interval; auto-merge; }' else - $NFT flush set inet ss_spec ss_spec_wan_ac 2>/dev/null + $NFT flush set inet ss_spec ss_spec_wan_ac_tcp 2>/dev/null fi # Add special IP ranges to WAN AC set for ip in $(gen_spec_iplist); do - [ -n "$ip" ] && $NFT add element inet ss_spec ss_spec_wan_ac "{ $ip }" 2>/dev/null + [ -n "$ip" ] && $NFT add element inet ss_spec ss_spec_wan_ac_tcp "{ $ip }" 2>/dev/null done - $NFT add rule inet ss_spec ss_spec_wan_ac ip daddr @ss_spec_wan_ac return - $NFT add rule inet ss_spec ss_spec_wan_ac ip daddr @china return - if $NFT list chain inet ss_spec ss_spec_wan_fw >/dev/null 2>&1; then - $NFT add rule inet ss_spec ss_spec_wan_ac ip saddr @gmlan ip daddr != @china jump ss_spec_wan_fw - $NFT add rule inet ss_spec ss_spec_wan_ac jump ss_spec_wan_fw + $NFT add rule inet ss_spec ss_spec_wan_ac_tcp ip daddr @ss_spec_wan_ac_tcp return + $NFT add rule inet ss_spec ss_spec_wan_ac_tcp ip daddr @china return + if $NFT list chain inet ss_spec ss_spec_wan_ac_tcp >/dev/null 2>&1; then + $NFT add rule inet ss_spec ss_spec_wan_ac_tcp ip saddr @gmlan ip daddr != @china jump ss_spec_wan_fw_tcp + $NFT add rule inet ss_spec ss_spec_wan_ac_tcp jump ss_spec_wan_fw_tcp + fi + if ! $NFT list set inet ss_spec ss_spec_wan_ac_udp >/dev/null 2>&1; then + $NFT add set inet ss_spec ss_spec_wan_ac_udp '{ type ipv4_addr; flags interval; auto-merge; }' + else + $NFT flush set inet ss_spec ss_spec_wan_ac_udp 2>/dev/null + fi + # Add special IP ranges to WAN AC set + for ip in $(gen_spec_iplist); do + [ -n "$ip" ] && $NFT add element inet ss_spec ss_spec_wan_ac_udp "{ $ip }" 2>/dev/null + done + $NFT add rule inet ss_spec ss_spec_wan_ac_udp ip daddr @ss_spec_wan_ac_udp return + $NFT add rule inet ss_spec ss_spec_wan_ac_udp ip daddr @china return + if $NFT list chain inet ss_spec ss_spec_wan_fw_udp >/dev/null 2>&1; then + $NFT add rule inet ss_spec ss_spec_wan_ac_udp ip saddr @gmlan ip daddr != @china jump ss_spec_wan_fw_udp + $NFT add rule inet ss_spec ss_spec_wan_ac_udp jump ss_spec_wan_fw_udp fi ;; gfw) if ! $NFT list set inet ss_spec gfwlist >/dev/null 2>&1; then $NFT add set inet ss_spec gfwlist '{ type ipv4_addr; flags interval; auto-merge; }' 2>/dev/null fi - $NFT add rule inet ss_spec ss_spec_wan_ac ip daddr @china return - $NFT add rule inet ss_spec ss_spec_wan_ac ip daddr @gfwlist jump ss_spec_wan_fw - $NFT add rule inet ss_spec ss_spec_wan_ac ip saddr @gmlan ip daddr != @china jump ss_spec_wan_fw + $NFT add rule inet ss_spec ss_spec_wan_ac_tcp ip daddr @china return + $NFT add rule inet ss_spec ss_spec_wan_ac_tcp ip daddr @gfwlist jump ss_spec_wan_fw_tcp + $NFT add rule inet ss_spec ss_spec_wan_ac_tcp ip saddr @gmlan ip daddr != @china jump ss_spec_wan_fw_tcp + $NFT add rule inet ss_spec ss_spec_wan_ac_udp ip daddr @china return + $NFT add rule inet ss_spec ss_spec_wan_ac_udp ip daddr @gfwlist jump ss_spec_wan_fw_udp + $NFT add rule inet ss_spec ss_spec_wan_ac_udp ip saddr @gmlan ip daddr != @china jump ss_spec_wan_fw_udp ;; oversea) if ! $NFT list set inet ss_spec oversea >/dev/null 2>&1; then $NFT add set inet ss_spec oversea '{ type ipv4_addr; flags interval; auto-merge; }' 2>/dev/null fi - $NFT add rule inet ss_spec ss_spec_wan_ac ip daddr @oversea jump ss_spec_wan_fw - $NFT add rule inet ss_spec ss_spec_wan_ac ip saddr @gmlan jump ss_spec_wan_fw - $NFT add rule inet ss_spec ss_spec_wan_ac ip daddr @china jump ss_spec_wan_fw + $NFT add rule inet ss_spec ss_spec_wan_ac_tcp ip daddr @oversea jump ss_spec_wan_fw_tcp + $NFT add rule inet ss_spec ss_spec_wan_ac_tcp ip saddr @gmlan jump ss_spec_wan_fw_tcp + $NFT add rule inet ss_spec ss_spec_wan_ac_tcp ip daddr @china jump ss_spec_wan_fw_tcp + $NFT add rule inet ss_spec ss_spec_wan_ac_udp ip daddr @oversea jump ss_spec_wan_fw_udp + $NFT add rule inet ss_spec ss_spec_wan_ac_udp ip saddr @gmlan jump ss_spec_wan_fw_udp + $NFT add rule inet ss_spec ss_spec_wan_ac_udp ip daddr @china jump ss_spec_wan_fw_udp ;; all) - if $NFT list chain inet ss_spec ss_spec_wan_fw >/dev/null 2>&1; then - $NFT add rule inet ss_spec ss_spec_wan_ac jump ss_spec_wan_fw + if $NFT list chain inet ss_spec ss_spec_wan_fw_tcp >/dev/null 2>&1; then + $NFT add rule inet ss_spec ss_spec_wan_ac_tcp jump ss_spec_wan_fw_tcp + fi + if $NFT list chain inet ss_spec ss_spec_wan_fw_udp >/dev/null 2>&1; then + $NFT add rule inet ss_spec ss_spec_wan_ac_udp jump ss_spec_wan_fw_udp fi ;; esac @@ -402,28 +472,68 @@ fw_rule() { } fw_rule_nft() { - # Exclude special local addresses - if $NFT list chain inet ss_spec ss_spec_wan_fw >/dev/null 2>&1; then - for net in 0.0.0.0/8 10.0.0.0/8 127.0.0.0/8 169.254.0.0/16 172.16.0.0/12 192.168.0.0/16 224.0.0.0/4 240.0.0.0/4; do - $NFT add rule inet ss_spec ss_spec_wan_fw ip daddr $net return 2>/dev/null - done + # set up routing table for tproxy + if ! ip rule show | grep -Eq "fwmark 0x0*1.*lookup 100"; then + ip rule add fwmark 0x01/0x01 table 100 2>/dev/null + fi + + if ! ip route show table 100 | grep -q "^local.*dev lo"; then + ip route add local 0.0.0.0/0 dev lo table 100 2>/dev/null fi # redirect/translation: when PROXY_PORTS present, redirect those tcp ports to local_port if [ -n "$PROXY_PORTS" ]; then - PORTS=$(echo "$PROXY_PORTS" | sed 's/-m multiport --dports //') - RULE="tcp dport { $PORTS } counter redirect to :"$local_port"" + PORTS_ARGS=$(echo "$PROXY_PORTS" | sed 's/-m multiport --dports //') + if [ -n "$PORTS_ARGS" ]; then + TCP_EXT_ARGS="meta l4proto tcp tcp dport { $PORTS_ARGS }" + UDP_EXT_ARGS="meta l4proto udp udp dport { $PORTS_ARGS }" + + TCP_RULE="meta l4proto tcp tcp dport { $PORTS_ARGS } counter redirect to :$local_port" + UDP_RULE="meta l4proto udp udp dport { $PORTS_ARGS } counter tproxy ip to :$local_port meta mark set 0x01" + fi else + TCP_EXT_ARGS="meta l4proto tcp" + UDP_EXT_ARGS="meta l4proto udp" + # default: redirect everything except ssh(22) - RULE="tcp dport != 22 counter redirect to :"$local_port"" + TCP_RULE="meta l4proto tcp tcp dport != 22 counter redirect to :$local_port" + # default: when PROXY_PORTS present, redirect those udp ports to local_port + UDP_RULE="meta l4proto udp counter tproxy ip to :$local_port meta mark set 0x01" fi - if ! $NFT list chain inet ss_spec ss_spec_wan_fw 2>/dev/null | grep -q "$RULE"; then - if ! $NFT add rule inet ss_spec ss_spec_wan_fw $RULE 2>/dev/null; then - loger 3 "Can't redirect, please check nftables." + # add TCP rule to fw chain if not exists (use -F exact match) + if ! $NFT list chain inet ss_spec ss_spec_wan_fw_tcp 2>/dev/null | grep -F -- "$TCP_RULE" >/dev/null 2>&1; then + if ! $NFT add rule inet ss_spec ss_spec_wan_fw_tcp $TCP_RULE 2>/dev/null; then + loger 3 "Can't redirect TCP, please check nftables." return 1 fi fi + if ! $NFT list chain inet ss_spec ss_spec_wan_fw_udp 2>/dev/null | grep -F -- "$UDP_RULE" >/dev/null 2>&1; then + if ! $NFT add rule inet ss_spec ss_spec_wan_fw_udp $UDP_RULE 2>/dev/null; then + loger 3 "Can't tproxy UDP, please check nftables." + return 1 + fi + fi + + if [ "$SHUNT_PORT" != "0" ] && [ -f "$SHUNT_LIST" ]; then + case "$SHUNT_PORT" in + 1) + $NFT add rule inet ss_spec ss_spec_wan_ac_tcp $TCP_EXT_ARGS ip daddr @netflix counter redirect to :$local_port + $NFT add rule inet ss_spec ss_spec_wan_ac_udp $UDP_EXT_ARGS ip daddr @netflix counter tproxy ip to :$local_port meta mark set 0x01 + ;; + *) + $NFT add rule inet ss_spec ss_spec_wan_ac_tcp $TCP_EXT_ARGS ip daddr @netflix counter redirect to :$SHUNT_PORT + $NFT add rule inet ss_spec ss_spec_wan_ac_udp $UDP_EXT_ARGS ip daddr @netflix counter tproxy ip to :$SHUNT_PORT meta mark set 0x01 + if [ "$SHUNT_PROXY" = "1" ]; then + $NFT add rule inet ss_spec ss_spec_wan_ac_tcp $TCP_EXT_ARGS ip daddr $SHUNT_IP counter redirect to :$local_port + $NFT add rule inet ss_spec ss_spec_wan_ac_udp $UDP_EXT_ARGS ip daddr $SHUNT_IP counter tproxy ip to :$local_port meta mark set 0x01 + else + [ -n "$SHUNT_IP" ] && $NFT add element inet ss_spec whitelist "{ $SHUNT_IP }" 2>/dev/null + fi + ;; + esac + fi + return $? } @@ -482,34 +592,67 @@ ac_rule_nft() { esac fi - # 创建ss_spec_prerouting链 - if ! $NFT list chain inet ss_spec ss_spec_prerouting >/dev/null 2>&1; then - $NFT add chain inet ss_spec ss_spec_prerouting '{ type nat hook prerouting priority 0; policy accept; }' + # Create ss_spec_prerouting tcp chain + if ! $NFT list chain inet ss_spec ss_spec_prerouting_tcp >/dev/null 2>&1; then + $NFT add chain inet ss_spec ss_spec_prerouting_tcp '{ type nat hook prerouting priority 0; policy accept; }' fi - $NFT flush chain inet ss_spec ss_spec_prerouting 2>/dev/null + $NFT flush chain inet ss_spec ss_spec_prerouting_tcp 2>/dev/null - # 创建ss_spec_output链 - if ! $NFT list chain inet ss_spec ss_spec_output >/dev/null 2>&1; then - $NFT add chain inet ss_spec ss_spec_output '{ type nat hook output priority 0; policy accept; }' + # Exclude special local addresses + if $NFT list chain inet ss_spec ss_spec_prerouting_tcp >/dev/null 2>&1; then + for net in 0.0.0.0/8 10.0.0.0/8 127.0.0.0/8 169.254.0.0/16 172.16.0.0/12 192.168.0.0/16 224.0.0.0/4 240.0.0.0/4; do + $NFT add rule inet ss_spec ss_spec_prerouting_tcp ip daddr $net return 2>/dev/null + done fi - $NFT flush chain inet ss_spec ss_spec_output 2>/dev/null + + # 暂注释 IPV6 用于后续开启 IPV6 + #if $NFT list chain inet ss_spec ss_spec_prerouting_tcp >/dev/null 2>&1; then + # for net in ::1/128 fe80::/10 fc00::/7 ff00::/8 ::/128 ::ffff:0:0/96; do + # $NFT add rule inet ss_spec ss_spec_prerouting_tcp ip6 daddr $net return 2>/dev/null + # done + #fi + + # Create ss_spec_prerouting udp chain + if ! $NFT list chain inet ss_spec ss_spec_prerouting_udp >/dev/null 2>&1; then + $NFT add chain inet ss_spec ss_spec_prerouting_udp '{ type filter hook prerouting priority -150; policy accept; }' + fi + $NFT flush chain inet ss_spec ss_spec_prerouting_udp 2>/dev/null + + # Exclude special local addresses + if $NFT list chain inet ss_spec ss_spec_prerouting_udp >/dev/null 2>&1; then + for net in 0.0.0.0/8 10.0.0.0/8 127.0.0.0/8 169.254.0.0/16 172.16.0.0/12 192.168.0.0/16 224.0.0.0/4 240.0.0.0/4; do + $NFT add rule inet ss_spec ss_spec_prerouting_udp ip daddr $net return 2>/dev/null + done + fi + + # 暂注释 IPV6 用于后续开启 IPV6 + #if $NFT list chain inet ss_spec ss_spec_prerouting_udp >/dev/null 2>&1; then + # for net in ::1/128 fe80::/10 fc00::/7 ff00::/8 ::/128 ::ffff:0:0/96; do + # $NFT add rule inet ss_spec ss_spec_prerouting_udp ip6 daddr $net return 2>/dev/null + # done + #fi # Build a rule in the prerouting hook chain that jumps to business chain with conditions - EXT_ARGS="" if [ -n "$PROXY_PORTS" ]; then PORTS_ARGS=$(echo "$PROXY_PORTS" | sed 's/-m multiport --dports //') if [ -n "$PORTS_ARGS" ]; then - EXT_ARGS="th dport { $PORTS_ARGS }" + TCP_EXT_ARGS="meta l4proto tcp tcp dport { $PORTS_ARGS }" + UDP_EXT_ARGS="meta l4proto udp udp dport { $PORTS_ARGS }" fi + else + TCP_EXT_ARGS="meta l4proto tcp" + UDP_EXT_ARGS="meta l4proto udp" fi if [ -z "$Interface" ]; then # generic prerouting jump already exists (see ipset_nft), but if we have MATCH_SET_CONDITION we add a more specific rule if [ -n "$MATCH_SET" ]; then # add a more specific rule at the top of ss_spec_prerouting - $NFT insert rule inet ss_spec ss_spec_prerouting meta l4proto tcp $EXT_ARGS $MATCH_SET jump ss_spec_wan_ac comment "\"$TAG\"" 2>/dev/null + $NFT add rule inet ss_spec ss_spec_prerouting_tcp $TCP_EXT_ARGS $MATCH_SET jump ss_spec_wan_ac_tcp comment "\"$TAG\"" 2>/dev/null + $NFT add rule inet ss_spec ss_spec_prerouting_udp $UDP_EXT_ARGS $MATCH_SET jump ss_spec_wan_ac_udp comment "\"$TAG\"" 2>/dev/null else - $NFT insert rule inet ss_spec ss_spec_prerouting meta l4proto tcp $EXT_ARGS jump ss_spec_wan_ac comment "\"$TAG\"" 2>/dev/null + $NFT add rule inet ss_spec ss_spec_prerouting_tcp $TCP_EXT_ARGS jump ss_spec_wan_ac_tcp comment "\"$TAG\"" 2>/dev/null + $NFT add rule inet ss_spec ss_spec_prerouting_udp $UDP_EXT_ARGS jump ss_spec_wan_ac_udp comment "\"$TAG\"" 2>/dev/null fi else # For each Interface, find its actual ifname and add an iifname-limited prerouting rule @@ -518,9 +661,11 @@ ac_rule_nft() { [ -z "$IFNAME" ] && IFNAME=$(uci -P /var/state get network."$name".device 2>/dev/null) if [ -n "$IFNAME" ]; then if [ -n "$MATCH_SET" ]; then - $NFT insert rule inet ss_spec ss_spec_prerouting meta iifname "$IFNAME" meta l4proto tcp $EXT_ARGS $MATCH_SET jump ss_spec_wan_ac comment "\"$TAG\"" 2>/dev/null + $NFT add rule inet ss_spec ss_spec_prerouting_tcp meta iifname "$IFNAME" $TCP_EXT_ARGS $MATCH_SET jump ss_spec_wan_ac_tcp comment "\"$TAG\"" 2>/dev/null + $NFT add rule inet ss_spec ss_spec_prerouting_udp meta iifname "$IFNAME" $UDP_EXT_ARGS $MATCH_SET jump ss_spec_wan_ac_udp comment "\"$TAG\"" 2>/dev/null else - $NFT insert rule inet ss_spec ss_spec_prerouting meta iifname "$IFNAME" meta l4proto tcp $EXT_ARGS jump ss_spec_wan_ac comment "\"$TAG\"" 2>/dev/null + $NFT add rule inet ss_spec ss_spec_prerouting_tcp meta iifname "$IFNAME" $TCP_EXT_ARGS jump ss_spec_wan_ac_tcp comment "\"$TAG\"" 2>/dev/null + $NFT add rule inet ss_spec ss_spec_prerouting_udp meta iifname "$IFNAME" $UDP_EXT_ARGS jump ss_spec_wan_ac_udp comment "\"$TAG\"" 2>/dev/null fi fi done @@ -528,8 +673,51 @@ ac_rule_nft() { case "$OUTPUT" in 1) + # Create ss_spec_output tcp chain + if ! $NFT list chain inet ss_spec ss_spec_output_tcp >/dev/null 2>&1; then + $NFT add chain inet ss_spec ss_spec_output_tcp '{ type nat hook output priority 0; policy accept; }' + fi + $NFT flush chain inet ss_spec ss_spec_output_tcp 2>/dev/null + + # Exclude special local addresses + if $NFT list chain inet ss_spec ss_spec_output_tcp >/dev/null 2>&1; then + for net in 0.0.0.0/8 10.0.0.0/8 127.0.0.0/8 169.254.0.0/16 172.16.0.0/12 192.168.0.0/16 224.0.0.0/4 240.0.0.0/4; do + $NFT add rule inet ss_spec ss_spec_output_tcp ip daddr $net return 2>/dev/null + done + fi + + # 暂注释 IPV6 用于后续开启 IPV6 + #if $NFT list chain inet ss_spec ss_spec_output_tcp >/dev/null 2>&1; then + # for net in ::1/128 fe80::/10 fc00::/7 ff00::/8 ::/128 ::ffff:0:0/96; do + # $NFT add rule inet ss_spec ss_spec_output_tcp ip6 daddr $net return 2>/dev/null + # done + #fi + # create output hook chain & route output traffic into router chain - $NFT insert rule inet ss_spec ss_spec_output meta l4proto tcp $EXT_ARGS jump ss_spec_wan_ac comment "\"$TAG\"" 2>/dev/null + $NFT add rule inet ss_spec ss_spec_output_tcp $TCP_EXT_ARGS jump ss_spec_wan_ac_tcp comment "\"$TAG\"" 2>/dev/null + + # Create ss_spec_output udp chain + if ! $NFT list chain inet ss_spec ss_spec_output_udp >/dev/null 2>&1; then + $NFT add chain inet ss_spec ss_spec_output_udp '{ type filter hook output priority -150; policy accept; }' + fi + $NFT flush chain inet ss_spec ss_spec_output_udp 2>/dev/null + + # Exclude special local addresses + if $NFT list chain inet ss_spec ss_spec_output_udp >/dev/null 2>&1; then + for net in 0.0.0.0/8 10.0.0.0/8 127.0.0.0/8 169.254.0.0/16 172.16.0.0/12 192.168.0.0/16 224.0.0.0/4 240.0.0.0/4; do + $NFT add rule inet ss_spec ss_spec_output_udp ip daddr $net return 2>/dev/null + done + fi + + # 暂注释 IPV6 用于后续开启 IPV6 + #if $NFT list chain inet ss_spec ss_spec_output_udp >/dev/null 2>&1; then + # for net in ::1/128 fe80::/10 fc00::/7 ff00::/8 ::/128 ::ffff:0:0/96; do + # $NFT add rule inet ss_spec ss_spec_output_udp ip6 daddr $net return 2>/dev/null + # done + #fi + + # create output hook chain & route output traffic into router chain + $NFT add rule inet ss_spec ss_spec_output_udp $UDP_EXT_ARGS meta mark set 0x01 comment "\"$TAG\"" 2>/dev/null ;; 2) # router mode output chain: create ssr_gen_router set & router chain @@ -539,8 +727,10 @@ ac_rule_nft() { done $NFT add chain inet ss_spec ss_spec_router 2>/dev/null $NFT add rule inet ss_spec ss_spec_router ip daddr @ssr_gen_router return 2>/dev/null - $NFT add rule inet ss_spec ss_spec_router jump ss_spec_wan_fw 2>/dev/null - $NFT add rule inet ss_spec ss_spec_output meta l4proto tcp $EXT_ARGS jump ss_spec_router comment "\"$TAG\"" 2>/dev/null + $NFT add rule inet ss_spec ss_spec_router jump ss_spec_wan_fw_tcp 2>/dev/null + $NFT add rule inet ss_spec ss_spec_output $TCP_EXT_ARGS jump ss_spec_router comment "\"$TAG\"" 2>/dev/null + $NFT add rule inet ss_spec ss_spec_router jump ss_spec_wan_fw_udp 2>/dev/null + $NFT add rule inet ss_spec ss_spec_output $UDP_EXT_ARGS jump ss_spec_router comment "\"$TAG\"" 2>/dev/null ;; esac return 0 @@ -605,8 +795,13 @@ tp_rule() { tp_rule_nft() { # set up routing table for tproxy - ip rule add fwmark 0x01/0x01 table 100 2>/dev/null - ip route add local 0.0.0.0/0 dev lo table 100 2>/dev/null + if ! ip rule show | grep -Eq "fwmark 0x0*1.*lookup 100"; then + ip rule add fwmark 0x01/0x01 table 100 2>/dev/null + fi + + if ! ip route show table 100 | grep -q "^local.*dev lo"; then + ip route add local 0.0.0.0/0 dev lo table 100 2>/dev/null + fi # create mangle table and tproxy chain if ! $NFT list table ip ss_spec_mangle >/dev/null 2>&1; then @@ -615,20 +810,15 @@ tp_rule_nft() { local MATCH_SET="" - EXT_ARGS="" if [ -n "$PROXY_PORTS" ]; then PORTS_ARGS=$(echo "$PROXY_PORTS" | sed 's/-m multiport --dports //') if [ -n "$PORTS_ARGS" ]; then - EXT_ARGS="th dport { $PORTS_ARGS }" + EXT_ARGS="udp dport { $PORTS_ARGS }" else EXT_ARGS="" fi fi - # 有端口 => 1,无端口 => 0 - HAS_PORTS=0 - [ -n "$EXT_ARGS" ] && HAS_PORTS=1 - if [ -n "$LAN_AC_IP" ]; then # Create LAN access control set if needed if ! $NFT list set ip ss_spec_mangle ss_spec_lan_ac >/dev/null 2>&1; then @@ -664,7 +854,7 @@ tp_rule_nft() { fi done - # 批量导入中国IP列表 + # Bulk import china ip list safely (avoid huge single element limitation) if [ -f "${china_ip:=/etc/ssrplus/china_ssr.txt}" ]; then $NFT add element ip ss_spec_mangle china "{ $(tr '\n' ',' < "${china_ip}" | sed 's/,$//') }" 2>/dev/null fi @@ -682,12 +872,19 @@ tp_rule_nft() { done fi + # 暂注释 IPV6 用于后续开启 IPV6 + #if $NFT list chain ip ss_spec_mangle ss_spec_tproxy >/dev/null 2>&1; then + # for net in ::1/128 fe80::/10 fc00::/7 ff00::/8 fe80::/10 ::/128 ::ffff:0:0/96; do + # $NFT add rule ip ss_spec_mangle ss_spec_tproxy ip6 daddr $net return 2>/dev/null + # done + #fi + # basic return rules in tproxy chain - $NFT add rule ip ss_spec_mangle ss_spec_tproxy udp dport 53 return 2>/dev/null + $NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp dport 53 return 2>/dev/null # avoid redirecting to udp server address if [ -n "$server" ]; then - $NFT add rule ip ss_spec_mangle ss_spec_tproxy udp dport != 53 ip daddr "$server" return 2>/dev/null + $NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp dport != 53 ip daddr "$server" return 2>/dev/null fi # if server != SERVER add SERVER to whitelist set (so tproxy won't touch it) @@ -700,12 +897,7 @@ tp_rule_nft() { # access control and tproxy rules $NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp ip saddr @bplan return 2>/dev/null - - if [ $HAS_PORTS -eq 1 ]; then - $NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp $EXT_ARGS ip saddr @fplan counter tproxy ip to :"$LOCAL_PORT" meta mark set 0x01 - else - $NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp ip saddr @fplan counter tproxy ip to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null - fi + $NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp $EXT_ARGS ip saddr @fplan counter tproxy ip to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null # Handle different run modes for nftables case "$RUNMODE" in @@ -722,50 +914,38 @@ tp_rule_nft() { $NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp ip daddr @ss_spec_wan_ac return 2>/dev/null $NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp ip daddr @china return 2>/dev/null - $NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp dport 80 drop 2>/dev/null + $NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp udp dport 80 drop 2>/dev/null $NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp ip saddr @gmlan ip daddr != @china counter tproxy ip to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null - if [ $HAS_PORTS -eq 1 ]; then - $NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp $EXT_ARGS ip daddr != @ss_spec_wan_ac counter tproxy ip to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null - else - $NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp ip daddr != @ss_spec_wan_ac counter tproxy ip to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null - fi + $NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp $EXT_ARGS ip daddr != @ss_spec_wan_ac counter tproxy ip to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null ;; gfw) - $NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp ip daddr @china return 2>/dev/null - $NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp dport 80 drop 2>/dev/null - if [ $HAS_PORTS -eq 1 ]; then - $NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp $EXT_ARGS ip daddr @gfwlist counter tproxy ip to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null + if ! $NFT list set ip ss_spec_mangle gfwlist >/dev/null 2>&1; then + $NFT add set ip ss_spec_mangle gfwlist '{ type ipv4_addr; flags interval; auto-merge; }' 2>/dev/null fi + $NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp ip daddr @china return 2>/dev/null + $NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp udp dport 80 drop 2>/dev/null + $NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp $EXT_ARGS ip daddr @gfwlist counter tproxy ip to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null $NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp ip saddr @gmlan ip daddr != @china counter tproxy ip to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null ;; oversea) if ! $NFT list set ip ss_spec_mangle oversea >/dev/null 2>&1; then $NFT add set ip ss_spec_mangle oversea '{ type ipv4_addr; flags interval; auto-merge; }' 2>/dev/null fi - if ! $NFT list set ip ss_spec_mangle china >/dev/null 2>&1; then - $NFT add set ip ss_spec_mangle china '{ type ipv4_addr; flags interval; auto-merge; }' 2>/dev/null - fi - if [ $HAS_PORTS -eq 1 ]; then - $NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp $EXT_ARGS ip saddr @oversea counter tproxy ip to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null - $NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp $EXT_ARGS ip daddr @china counter tproxy ip to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null - fi + $NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp $EXT_ARGS ip saddr @oversea counter tproxy ip to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null + $NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp $EXT_ARGS ip daddr @china counter tproxy ip to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null $NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp ip saddr @gmlan counter tproxy ip to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null ;; all) - if [ $HAS_PORTS -eq 1 ]; then - $NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp $EXT_ARGS counter tproxy ip to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null - else - $NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp counter tproxy ip to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null - fi + $NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp $EXT_ARGS counter tproxy ip to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null ;; esac - # 创建 prerouting 链(hook prerouting) + # finally, ensure prerouting hook entry to jump to tproxy chain if ! $NFT list chain ip ss_spec_mangle prerouting >/dev/null 2>&1; then $NFT add chain ip ss_spec_mangle prerouting '{ type filter hook prerouting priority mangle; policy accept; }' fi - # 添加规则到 prerouting 链 + # add prerouting jump (idempotent) if [ -z "$Interface" ]; then # 全局规则 if [ -n "$MATCH_SET" ]; then @@ -884,17 +1064,23 @@ gen_include() { return $? } +# 修改gen_include_nft,调用持久化功能 gen_include_nft() { # Generate nftables include file for firewall4 [ -n "$FWI" ] && echo '#!/bin/sh' >"$FWI" - cat <<-'EOF' >>"$FWI" + cat <<-EOF >>"$FWI" # Clear existing ss_spec tables nft delete table inet ss_spec 2>/dev/null nft delete table ip ss_spec 2>/dev/null nft delete table ip ss_spec_mangle 2>/dev/null - # Restore shadowsocks nftables rules - nft list ruleset | awk '/^table (inet|ip) ss_spec/{flag=1} /^table / && !/^table (inet|ip) ss_spec/{flag=0} flag' + # Restore shadowsocks nftables rules from persistent file + if [ -f "/usr/share/nftables.d/ruleset-post/99-shadowsocksr.nft" ]; then + nft -f /usr/share/nftables.d/ruleset-post/99-shadowsocksr.nft + else + # Fallback: restore from current ruleset (filtered) + nft list ruleset | awk '/^table (inet|ip) ss_spec/{flag=1} /^table / && !/^table (inet|ip) ss_spec/{flag=0} flag' | nft -f - + fi EOF chmod +x "$FWI" } @@ -914,7 +1100,273 @@ gen_include_iptables() { EOF } -while getopts ":m:s:l:S:L:i:e:a:B:b:w:p:G:D:F:N:M:I:oOuUfgrczh" arg; do +# 检查 nftables 规则状态 +check_nftables_status() { + if [ "$USE_NFT" != "1" ]; then + echo "NFTables not in use" + return 0 + fi + + # 检查ss_spec表是否存在 + if ! $NFT list table inet ss_spec >/dev/null 2>&1 && \ + ! $NFT list table ip ss_spec_mangle >/dev/null 2>&1; then + echo "ss_spec tables missing in nftables" + return 1 + fi + + # 检查是否有基本规则 + if ! $NFT list table inet ss_spec 2>/dev/null | grep -q "chain.*ss_spec_wan_ac" || \ + ! $NFT list table inet ss_spec 2>/dev/null | grep -q "jump.*ss_spec_wan_fw"; then + echo "Basic SSR rules missing" + return 1 + fi + + echo "NFTables rules status: OK" + return 0 +} + +# 比较当前规则与持久化规则 +compare_rules() { + if [ "$USE_NFT" != "1" ]; then + return 1 # NFTables未使用,需要更新 + fi + + # 如果没有持久化文件,更新持久化文件 + if [ ! -f "$NFTABLES_RULES_FILE" ]; then + loger 6 "No persistence file found, update needed" + return 1 # 需要更新持久化文件 + fi + + # 检查ss_spec表是否存在 + if ! $NFT list table inet ss_spec >/dev/null 2>&1 && \ + ! $NFT list table ip ss_spec_mangle >/dev/null 2>&1; then + loger 6 "ss_spec tables missing, update needed" + return 1 # 需要更新ss_spec表 + fi + + # 生成当前规则的临时文件 + local temp_file=$(mktemp) + local rules_file=$(mktemp) + loger 7 "DEBUG: Temporary file path: $current_rules_file" + + # 导出当前规则到临时文件 + $NFT list ruleset | awk ' + /^table (inet ss_spec|ip ss_spec_mangle)/ {flag=1} + /^table / && !/^table (inet ss_spec|ip ss_spec_mangle)/ {flag=0} + flag + ' > "$rules_file" 2>/dev/null + + # 检查是否成功导出了当前规则 + if [ ! -s "$rules_file" ] || ! grep -q "table" "$rules_file" 2>/dev/null; then + loger 4 "Failed to export current rules" + rm -f "$temp_file" "$rules_file" + return 1 # 导出失败,需要更新 + fi + + # 比较当前规则与持久化文件中的规则 + if ! cmp -s "$rules_file" "$NFTABLES_RULES_FILE"; then + loger 6 "Rules differ, update needed" + rm -f "$temp_file" "$rules_file" + return 1 # 需要更新 + fi + + rm -f "$temp_file" "$rules_file" + loger 6 "Rules unchanged, no update needed" + return 0 # 无需更新 +} + +# 自动更新持久化规则 +persist_nftables_rules() { + if [ "$USE_NFT" != "1" ]; then + return 0 + fi + + # 如果模式未改变且存在持久化文件,跳过更新 + if [ "$MODE_CHANGED" = "0" ] && [ -f "$NFTABLES_RULES_FILE" ]; then + loger 6 "Mode unchanged and persistence file exists, skipping update" + return 0 + fi + + # 强制更新时,跳过比较检查并删除旧文件 + if [ "$FORCE_UPDATE" = "1" ]; then + loger 6 "Force update requested, removing old persistence file" + rm -f "$NFTABLES_RULES_FILE" 2>/dev/null + # 非强制更新时,进行规则比较 + elif [ -f "$NFTABLES_RULES_FILE" ]; then + if compare_rules; then + loger 6 "Rules unchanged, skipping persistence update" + return 0 + fi + fi + + # 确保目录存在 + mkdir -p "$NFTABLES_RULES_DIR" 2>/dev/null + + # 生成nftables规则文件 + cat <<-'EOF' >>$NFTABLES_RULES_FILE + #!/usr/sbin/nft -f + + # ShadowsocksR nftables rules + # Generated by ssr-rules script + EOF + + echo "# Auto-updated: $(date)" >> "$NFTABLES_RULES_FILE" + echo "# Runmode: ${RUNMODE:-router}" >> "$NFTABLES_RULES_FILE" + echo "# Server: $server, Port: $local_port" >> "$NFTABLES_RULES_FILE" + echo "" >> "$NFTABLES_RULES_FILE" + + local HAS_RULES=0 + + # 分别导出每个表 + if $NFT list table inet ss_spec >/dev/null 2>&1; then + loger 6 "Exporting table inet ss_spec" + { + echo "" + echo "# inet ss_spec table for main rules" + $NFT list table inet ss_spec 2>/dev/null + } >> "$NFTABLES_RULES_FILE" + HAS_RULES=1 + fi + + if $NFT list table ip ss_spec_mangle >/dev/null 2>&1; then + loger 6 "Exporting table ip ss_spec_mangle" + { + echo "" + echo "# ip ss_spec_mangle table for TPROXY rules" + $NFT list table ip ss_spec_mangle 2>/dev/null + } >> "$NFTABLES_RULES_FILE" + HAS_RULES=1 + fi + + # 检查是否成功导出了规则 + if [ $HAS_RULES -eq 0 ] || [ ! -s "$NFTABLES_RULES_FILE" ] || ! grep -q "table" "$NFTABLES_RULES_FILE" 2>/dev/null; then + loger 4 "No ss_spec nftables rules found to persist" + rm -f "$NFTABLES_RULES_FILE" 2>/dev/null + return 1 + fi + + # 设置文件权限 + chmod 644 "$NFTABLES_RULES_FILE" 2>/dev/null + + # 记录成功信息 + local TABLES=$(grep "^table" "$NFTABLES_RULES_FILE" | awk '{print $2 " " $3}' | tr '\n' ',' | sed 's/,$//') + loger 5 "NFTables rules persisted to $NFTABLES_RULES_FILE (Tables: $TABLES)" + + return 0 +} + +# 自动更新守护进程 +start_auto_update_daemon() { + if [ "$USE_NFT" != "1" ] || [ "$AUTO_UPDATE_INTERVAL" = "0" ]; then + return 0 + fi + + loger 6 "Starting nftables rules auto-update daemon" + + # 停止已经运行的守护进程 + stop_auto_update_daemon + + # 直接在后台启动守护进程 + ( + logger -t ssr-rules[daemon] "Auto-update daemon started - PID: $$" + echo $$ > "/var/run/ssr-rules-daemon.pid" + + while true; do + sleep 300 + if [ -x "/usr/bin/ssr-rules" ]; then + if /usr/bin/ssr-rules -C >/dev/null 2>&1; then + logger -t ssr-rules[daemon] "Rules changed or missing, updating persistence" + if /usr/bin/ssr-rules -P >/dev/null 2>&1; then + logger -t ssr-rules[daemon] "Persistence rules updated successfully" + else + logger -t ssr-rules[daemon] "Failed to update persistence" + fi + else + logger -t ssr-rules[daemon] "Rules status OK, no update needed" + fi + else + logger -t ssr-rules[daemon] "Script not found, exiting daemon" + exit 1 + fi + done + ) & + + local DAEMON_PID=$! + sleep 2 + + if kill -0 "$DAEMON_PID" 2>/dev/null; then + loger 6 "Auto-update daemon started with PID: $DAEMON_PID" + return 0 + else + loger 3 "Auto-update daemon failed to start" + return 1 + fi +} + +# 停止自动更新守护进程函数 +stop_auto_update_daemon() { + local PID_FILE="/var/run/ssr-rules-daemon.pid" + + if [ -f "$PID_FILE" ]; then + local DAEMON_PID=$(cat "$PID_FILE" 2>/dev/null) + if [ -n "$DAEMON_PID" ] && kill -0 "$DAEMON_PID" 2>/dev/null; then + kill "$DAEMON_PID" 2>/dev/null + loger 6 "Stopped auto-update daemon (PID: $DAEMON_PID)" + fi + rm -f "$PID_FILE" 2>/dev/null + fi + + loger 6 "Auto-update daemon stopped" +} + +# 强制更新持久化规则函数 +force_update_persistence() { + if [ "$USE_NFT" != "1" ]; then + echo "NFTables not in use" + return 0 + fi + + # 移除现有规则文件确保重新创建 + rm -f "$NFTABLES_RULES_FILE" 2>/dev/null + + # 调用持久化函数 + if persist_nftables_rules; then + loger 5 "Persistence update completed successfully" + return 0 + else + loger 3 "Persistence update failed" + return 1 + fi +} + +# 从持久化文件恢复规则 +restore_from_persistence() { + if [ "$USE_NFT" != "1" ]; then + loger 3 "NFTables not in use, cannot restore rules" + return 1 + fi + + if [ ! -f "$NFTABLES_RULES_FILE" ]; then + loger 4 "Persistence file not found: $NFTABLES_RULES_FILE" + return 1 + fi + + loger 6 "Restoring rules from persistence file" + + # 清理现有规则 + flush_r + + # 从文件恢复规则 + if $NFT -f "$NFTABLES_RULES_FILE" 2>/dev/null; then + loger 5 "Rules restored successfully from persistence file" + return 0 + else + loger 4 "Failed to restore rules from persistence file" + return 1 + fi +} + +while getopts ":m:s:l:S:L:i:e:a:B:b:w:p:G:D:F:N:M:I:oOuUfgrczAKPCRXh" arg; do case "$arg" in m) Interface=$OPTARG @@ -994,58 +1446,283 @@ while getopts ":m:s:l:S:L:i:e:a:B:b:w:p:G:D:F:N:M:I:oOuUfgrczh" arg; do z) RUNMODE=all ;; - f) - flush_r - exit 0 + # 新增持久化管理选项 + A) + ENABLE_AUTO_UPDATE=1 + ;; + K) + STOP_AUTO_UPDATE=1 + ;; + P) + FORCE_UPDATE=1 + ;; + C) + CHECK_STATUS=1 + ;; + R) + RESTORE_RULES=1 + ;; + X) + CLEANUP_PERSISTENCE=1 + ;; + f) + FLUSH_RULES=1 + ;; + h) + usage 0 ;; - h) usage 0 ;; esac done -if [ -z "$server" ] || [ -z "$local_port" ]; then - usage 2 +# 首先处理需要立即退出的选项 +if [ "$CHECK_STATUS" = "1" ]; then + check_nftables_status + exit $? fi -if ! echo "$local_port" | grep -qE '^[0-9]+$'; then - loger 3 "Invalid local port: $local_port" - exit 1 +if [ "$STOP_AUTO_UPDATE" = "1" ]; then + stop_auto_update_daemon + exit 0 fi -case "$TPROXY" in -1) - SERVER=$server - LOCAL_PORT=$local_port - ;; -2) - : ${SERVER:?"You must assign an ip for the udp relay server."} - : ${LOCAL_PORT:?"You must assign a port for the udp relay server."} - ;; -esac - -# First check whether nftables is working properly -if [ "$USE_NFT" = "1" ]; then - if ! $NFT list tables 2>/dev/null; then - loger 3 "nftables is not working properly, check if nftables is installed and running" - exit 1 - fi +# 只有-X选项,执行清理后退出 +if [ "$CLEANUP_PERSISTENCE" = "1" ] && [ "$FLUSH_RULES" != "1" ] && [ -z "$server" ] && [ -z "$local_port" ] && \ + [ "$FORCE_UPDATE" != "1" ] && [ "$RESTORE_RULES" != "1" ] && [ "$ENABLE_AUTO_UPDATE" != "1" ]; then + cleanup_persistence_files + exit $? fi -if [ "$USE_NFT" = "1" ]; then - # NFTables - if flush_r && ipset_r && fw_rule && ac_rule && tp_rule && gen_include; then - loger 5 "NFTables rules applied successfully" - exit 0 +# 检查是否有持久化管理选项单独处理 +PERSISTENCE_ONLY=0 +if [ -z "$server" ] && [ -z "$local_port" ] && [ "$FLUSH_RULES" != "1" ]; then + if [ "$FORCE_UPDATE" = "1" ] || [ "$RESTORE_RULES" = "1" ] || [ "$ENABLE_AUTO_UPDATE" = "1" ] || [ "$CLEANUP_PERSISTENCE" = "1" ]; then + PERSISTENCE_ONLY=1 else - loger 3 "NFTables setup failed!" - exit 1 - fi -else - # iptables - if flush_r && fw_rule && ipset_r && ac_rule && tp_rule && gen_include; then - loger 5 "iptables rules applied successfully" - exit 0 - else - loger 3 "iptables setup failed!" - exit 1 + usage 2 + fi +fi + +# 处理持久化管理选项的情况 +if [ "$PERSISTENCE_ONLY" = "1" ]; then + if [ "$FORCE_UPDATE" = "1" ]; then + force_update_persistence + exit $? + fi + + if [ "$RESTORE_RULES" = "1" ]; then + restore_from_persistence + exit $? + fi + + if [ "$ENABLE_AUTO_UPDATE" = "1" ]; then + start_auto_update_daemon + exit $? + fi +fi + +# 强制刷新规则 +if [ "$FLUSH_RULES" = "1" ]; then + flush_r + # 如果只有 -f 选项,则退出 + if [ -z "$server" ] && [ -z "$local_port" ] && [ "$FORCE_UPDATE" != "1" ] && \ + [ "$RESTORE_RULES" != "1" ] && [ "$ENABLE_AUTO_UPDATE" != "1" ] && \ + [ "$CLEANUP_PERSISTENCE" != "1" ]; then + exit 0 + fi +fi + +# 从持久化文件恢复规则(在规则应用之前) +if [ "$RESTORE_RULES" = "1" ]; then + restore_from_persistence + if [ $? -ne 0 ]; then + loger 3 "Failed to restore from persistence, continuing with rule application" + fi +fi + +# 运行模式更改 +runmode_change() { + local mode_file="/tmp/.ssr_run_mode" + local new_mode="" + local old_mode="" + + # 从参数获取模式 + if [ -n "$1" ]; then + new_mode="$1" + fi + + # 从文件中读取上一次的运行模式 + if [ -f "$mode_file" ]; then + old_mode=$(cat "$mode_file" 2>/dev/null) + fi + + # 比较模式是否改变 + if [ "$old_mode" = "$new_mode" ] && [ -n "$old_mode" ]; then + # 模式未改变 + echo "$new_mode" > "$mode_file" # 更新文件时间戳 + loger 6 "Runmode unchanged: $new_mode" + return 1 # 返回1表示未改变 + else + # 模式已改变或首次运行 + echo "$new_mode" > "$mode_file" + if [ -n "$old_mode" ]; then + loger 6 "Runmode changed from '$old_mode' to '$new_mode'" + else + loger 6 "Runmode set to '$new_mode'" + fi + return 0 # 返回0表示已改变 + fi +} + +# Main process +if [ -n "$server" ] && [ -n "$local_port" ]; then + if ! echo "$local_port" | grep -qE '^[0-9]+$'; then + loger 3 "Invalid local port: $local_port" + exit 1 + fi + + case "$TPROXY" in + 1) + SERVER=$server + LOCAL_PORT=$local_port + ;; + 2) + : ${SERVER:?"You must assign an ip for the udp relay server."} + : ${LOCAL_PORT:?"You must assign a port for the udp relay server."} + ;; + esac + + if [ "$USE_NFT" = "1" ]; then + # NFTables + # 保存上一次 TPROXY 状态文件 + TPROXY_STATE_FILE="/tmp/.last_tproxy" + if [ -f "$TPROXY_STATE_FILE" ]; then + LAST_TPROXY=$(cat "$TPROXY_STATE_FILE") + else + LAST_TPROXY="" + fi + + # 保存上一次 PROXY_PORTS 状态 + PROXY_PORTS_STATE_FILE="/tmp/.last_proxy_ports" + if [ -f "$PROXY_PORTS_STATE_FILE" ]; then + LAST_PROXY_PORTS=$(cat "$PROXY_PORTS_STATE_FILE") + else + LAST_PROXY_PORTS="" + fi + + # STEP 1: 判断 TPROXY 是否有值(1 或 2) + if [ "$TPROXY" = "1" ] || [ "$TPROXY" = "2" ]; then + TPROXY_HAS_VALUE=1 + else + TPROXY_HAS_VALUE=0 + fi + + if [ "$LAST_TPROXY" = "1" ] || [ "$LAST_TPROXY" = "2" ]; then + LAST_HAS_VALUE=1 + else + LAST_HAS_VALUE=0 + fi + + # STEP 2: 判断 PROXY_PORTS 是否有值(非空字符串) + if [ -n "${PROXY_PORTS// }" ]; then + PROXY_HAS_VALUE=1 + else + PROXY_HAS_VALUE=0 + fi + + if [ -n "${LAST_PROXY_PORTS// }" ]; then + LAST_PROXY_HAS_VALUE=1 + else + LAST_PROXY_HAS_VALUE=0 + fi + + # STEP 3: 判断是否需要强制重建 + FORCE_RECREATE=0 + PERSISTENCE_EXISTS=0 + + # 触发条件: + # 1. TPROXY 从空 ↔ 有值变化 + # 2. PROXY_PORTS 从空 ↔ 有值变化 + if [ "$TPROXY_HAS_VALUE" != "$LAST_HAS_VALUE" ] || [ "$PROXY_HAS_VALUE" != "$LAST_PROXY_HAS_VALUE" ]; then + FORCE_RECREATE=1 + loger 6 "TPROXY or PROXY_PORTS changed → force rebuild rules" + rm -f "$NFTABLES_RULES_FILE" 2>/dev/null + else + # 未触发 FORCE_RECREATE → 检查持久化文件 + if [ -f "$NFTABLES_RULES_FILE" ] && [ -s "$NFTABLES_RULES_FILE" ]; then + PERSISTENCE_EXISTS=1 + loger 6 "Persistence file exists: $NFTABLES_RULES_FILE" + else + PERSISTENCE_EXISTS=0 + loger 6 "Persistence file does not exist or empty" + fi + fi + + # STEP 4: 保存当前状态 + echo "$TPROXY" > "$TPROXY_STATE_FILE" + echo "$PROXY_PORTS" > "$PROXY_PORTS_STATE_FILE" + + # STEP 5: 判断运行模式是否改变 + if runmode_change "$RUNMODE"; then + MODE_CHANGED=1 + loger 6 "Runmode changed: MODE_CHANGED=1" + else + MODE_CHANGED=0 + loger 6 "Runmode unchanged: MODE_CHANGED=0" + fi + + # STEP 6: 模式改变且持久化存在 → 删除一次 + if [ "$MODE_CHANGED" = "1" ] && [ "$PERSISTENCE_EXISTS" = "1" ]; then + loger 6 "Mode changed → removing persistence file" + rm -f "$NFTABLES_RULES_FILE" + PERSISTENCE_EXISTS=0 + fi + + # STEP 7: FORCE_RECREATE 优先 → 必须重建规则 + 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 + loger 5 "NFT rules applied successfully (forced rebuild)" + persist_nftables_rules + [ "$ENABLE_AUTO_UPDATE" = "1" ] && start_auto_update_daemon + exit 0 + else + loger 3 "NFT forced rebuild failed!" + exit 1 + fi + fi + + # STEP 8: 持久化存在 → 尝试 restore + if [ "$PERSISTENCE_EXISTS" = "1" ]; then + # 恢复规则 + if restore_from_persistence; then + loger 5 "NFT rules restored from persistence" + gen_include + [ "$ENABLE_AUTO_UPDATE" = "1" ] && start_auto_update_daemon + exit 0 + else + loger 3 "Restore failed → fallback to full setup" + PERSISTENCE_EXISTS=0 + fi + fi + + # STEP 9: 持久化不存在或 restore 失败 → 生成新规则 + if flush_r && ipset_r && fw_rule && ac_rule && tp_rule && gen_include; then + loger 5 "NFTables rules applied successfully" + persist_nftables_rules + [ "$ENABLE_AUTO_UPDATE" = "1" ] && start_auto_update_daemon + exit 0 + else + loger 3 "NFTables setup failed!" + exit 1 + fi + else + # iptables + if flush_r && fw_rule && ipset_r && ac_rule && tp_rule && gen_include; then + loger 5 "iptables rules applied successfully" + exit 0 + else + loger 3 "iptables setup failed!" + exit 1 + fi fi fi diff --git a/small/luci-app-ssr-plus/root/usr/share/shadowsocksr/update.lua b/small/luci-app-ssr-plus/root/usr/share/shadowsocksr/update.lua index 65e98a7cc6..34d13dc89e 100755 --- a/small/luci-app-ssr-plus/root/usr/share/shadowsocksr/update.lua +++ b/small/luci-app-ssr-plus/root/usr/share/shadowsocksr/update.lua @@ -213,7 +213,9 @@ local function update(url, file, type, file2) if type == "gfw_data" or type == "ad_data" then luci.sys.call("/usr/share/shadowsocksr/gfw2ipset.sh") else - luci.sys.call("/usr/share/shadowsocksr/chinaipset.sh " .. TMP_PATH .. "/china_ssr.txt") + if luci.sys.call("command -v ipset >/dev/null 2>&1") == 0 then + luci.sys.call("/usr/share/shadowsocksr/chinaipset.sh " .. TMP_PATH .. "/china_ssr.txt") + end end if args then log(0, tonumber(icount) / Num) diff --git a/small/v2ray-geodata/Makefile b/small/v2ray-geodata/Makefile index 5f6967a879..d183411a0c 100644 --- a/small/v2ray-geodata/Makefile +++ b/small/v2ray-geodata/Makefile @@ -21,13 +21,13 @@ define Download/geoip HASH:=6878dbacfb1fcb1ee022f63ed6934bcefc95a3c4ba10c88f1131fb88dbf7c337 endef -GEOSITE_VER:=20251206075552 +GEOSITE_VER:=20251207105427 GEOSITE_FILE:=dlc.dat.$(GEOSITE_VER) define Download/geosite URL:=https://github.com/v2fly/domain-list-community/releases/download/$(GEOSITE_VER)/ URL_FILE:=dlc.dat FILE:=$(GEOSITE_FILE) - HASH:=f1276502c556709de5cc6c7581cb2e9369721c37dc6142aacf5771f7c60106f6 + HASH:=546daff727fb5068cd53e10c471d9439e4009d77286ad33b5fd43194cf874221 endef GEOSITE_IRAN_VER:=202512010051 diff --git a/v2rayn/v2rayN/Directory.Build.props b/v2rayn/v2rayN/Directory.Build.props index 74e37384af..eec3a432a4 100644 --- a/v2rayn/v2rayN/Directory.Build.props +++ b/v2rayn/v2rayN/Directory.Build.props @@ -1,7 +1,7 @@ - 7.16.5 + 7.16.6 diff --git a/v2rayn/v2rayN/Directory.Packages.props b/v2rayn/v2rayN/Directory.Packages.props index beb870f2ed..c842bee0b9 100644 --- a/v2rayn/v2rayN/Directory.Packages.props +++ b/v2rayn/v2rayN/Directory.Packages.props @@ -12,13 +12,13 @@ - + - + - + diff --git a/v2rayn/v2rayN/ServiceLib/Global.cs b/v2rayn/v2rayN/ServiceLib/Global.cs index c96578d4c6..f0a2402859 100644 --- a/v2rayn/v2rayN/ServiceLib/Global.cs +++ b/v2rayn/v2rayN/ServiceLib/Global.cs @@ -586,7 +586,6 @@ public class Global public static readonly List IPAPIUrls = [ - @"https://speed.cloudflare.com/meta", @"https://api.ip.sb/geoip", @"https://api-ipv4.ip.sb/geoip", @"https://api-ipv6.ip.sb/geoip", diff --git a/v2rayn/v2rayN/ServiceLib/Manager/ActionPrecheckManager.cs b/v2rayn/v2rayN/ServiceLib/Manager/ActionPrecheckManager.cs index 7b665816a2..fd3436dbd0 100644 --- a/v2rayn/v2rayN/ServiceLib/Manager/ActionPrecheckManager.cs +++ b/v2rayn/v2rayN/ServiceLib/Manager/ActionPrecheckManager.cs @@ -12,8 +12,10 @@ public class ActionPrecheckManager(Config config) // sing-box supported transports for different protocol types private static readonly HashSet SingboxUnsupportedTransports = [nameof(ETransport.kcp), nameof(ETransport.xhttp)]; + private static readonly HashSet SingboxTransportSupportedProtocols = [EConfigType.VMess, EConfigType.VLESS, EConfigType.Trojan, EConfigType.Shadowsocks]; + private static readonly HashSet SingboxShadowsocksAllowedTransports = [nameof(ETransport.tcp), nameof(ETransport.ws), nameof(ETransport.quic)]; diff --git a/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs b/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs index b04c6e9ffd..f18f833c6f 100644 --- a/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs +++ b/v2rayn/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs @@ -365,7 +365,7 @@ public partial class CoreConfigSingboxService case nameof(ETransport.ws): transport.type = nameof(ETransport.ws); var wsPath = node.Path; - + // Parse eh and ed parameters from path using regex if (!wsPath.IsNullOrEmpty()) { diff --git a/v2rayn/v2rayN/v2rayN.Desktop/Common/AppBuilderExtension.cs b/v2rayn/v2rayN/v2rayN.Desktop/Common/AppBuilderExtension.cs index 5a3fca9527..4266b91fb5 100644 --- a/v2rayn/v2rayN/v2rayN.Desktop/Common/AppBuilderExtension.cs +++ b/v2rayn/v2rayN/v2rayN.Desktop/Common/AppBuilderExtension.cs @@ -1,9 +1,3 @@ -using System; -using System.Collections.Generic; -using System.IO; -using Avalonia; -using Avalonia.Media; - namespace v2rayN.Desktop.Common; public static class AppBuilderExtension @@ -11,7 +5,7 @@ public static class AppBuilderExtension public static AppBuilder WithFontByDefault(this AppBuilder appBuilder) { var fallbacks = new List(); - + var notoSansSc = new FontFamily(Path.Combine(Global.AvaAssets, "Fonts#Noto Sans SC")); fallbacks.Add(new FontFallback { FontFamily = notoSansSc }); diff --git a/v2rayn/v2rayN/v2rayN.Desktop/Views/AddGroupServerWindow.axaml b/v2rayn/v2rayN/v2rayN.Desktop/Views/AddGroupServerWindow.axaml index 82fc797086..bb95588dd8 100644 --- a/v2rayn/v2rayN/v2rayN.Desktop/Views/AddGroupServerWindow.axaml +++ b/v2rayn/v2rayN/v2rayN.Desktop/Views/AddGroupServerWindow.axaml @@ -34,7 +34,6 @@ @@ -89,7 +88,7 @@ - + - - + Margin="{StaticResource Margin4}" + IsEditable="True" /> this.Bind(ViewModel, vm => vm.GeoFileSourceUrl, v => v.cmbGetFilesSourceUrl.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SrsFileSourceUrl, v => v.cmbSrsFilesSourceUrl.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.RoutingRulesSourceUrl, v => v.cmbRoutingRulesSourceUrl.SelectedValue).DisposeWith(disposables); - this.Bind(ViewModel, vm => vm.IPAPIUrl, v => v.cmbIPAPIUrl.SelectedValue).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.IPAPIUrl, v => v.cmbIPAPIUrl.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.notProxyLocalAddress, v => v.tognotProxyLocalAddress.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.systemProxyAdvancedProtocol, v => v.cmbsystemProxyAdvancedProtocol.SelectedValue).DisposeWith(disposables); diff --git a/v2rayn/v2rayN/v2rayN/Views/AddGroupServerWindow.xaml b/v2rayn/v2rayN/v2rayN/Views/AddGroupServerWindow.xaml index 97f54407bc..841b3d84ce 100644 --- a/v2rayn/v2rayN/v2rayN/Views/AddGroupServerWindow.xaml +++ b/v2rayn/v2rayN/v2rayN/Views/AddGroupServerWindow.xaml @@ -134,7 +134,10 @@ - + @@ -188,9 +191,7 @@ Style="{StaticResource DefTextBox}" /> - - \d+)\.html' + _TESTS = [{ + 'url': 'https://www.alibaba.com/product-detail/Kids-Entertainment-Bouncer-Bouncy-Castle-Waterslide_1601271126969.html', + 'info_dict': { + 'id': '6000280444270', + 'display_id': '1601271126969', + 'ext': 'mp4', + 'title': 'Kids Entertainment Bouncer Bouncy Castle Waterslide Juex Gonflables Commercial Inflatable Tropical Water Slide', + 'duration': 30, + 'thumbnail': 'https://sc04.alicdn.com/kf/Hc5bb391974454af18c7a4f91cbe4062bg.jpg_120x120.jpg', + }, + }] + + def _real_extract(self, url): + display_id = self._match_id(url) + webpage = self._download_webpage(url, display_id) + product_data = self._search_json( + r'window\.detailData\s*=', webpage, 'detail data', display_id)['globalData']['product'] + + return { + **traverse_obj(product_data, ('mediaItems', lambda _, v: v['type'] == 'video' and v['videoId'], any, { + 'id': ('videoId', {int}, {str_or_none}), + 'duration': ('duration', {int_or_none}), + 'thumbnail': ('videoCoverUrl', {url_or_none}), + 'formats': ('videoUrl', lambda _, v: url_or_none(v['videoUrl']), { + 'url': 'videoUrl', + 'format_id': ('definition', {str_or_none}), + 'tbr': ('bitrate', {int_or_none}), + 'width': ('width', {int_or_none}), + 'height': ('height', {int_or_none}), + 'filesize': ('length', {int_or_none}), + }), + })), + 'title': traverse_obj(product_data, ('subject', {str})), + 'display_id': display_id, + } diff --git a/yt-dlp/yt_dlp/extractor/sportdeutschland.py b/yt-dlp/yt_dlp/extractor/sportdeutschland.py index 0b7d90a071..cba026c8f0 100644 --- a/yt-dlp/yt_dlp/extractor/sportdeutschland.py +++ b/yt-dlp/yt_dlp/extractor/sportdeutschland.py @@ -8,10 +8,11 @@ from ..utils import ( class SportDeutschlandIE(InfoExtractor): - _VALID_URL = r'https?://(?:player\.)?sportdeutschland\.tv/(?P(?:[^/?#]+/)?[^?#/&]+)' + IE_NAME = 'sporteurope' + _VALID_URL = r'https?://(?:player\.)?sporteurope\.tv/(?P(?:[^/?#]+/)?[^?#/&]+)' _TESTS = [{ # Single-part video, direct link - 'url': 'https://sportdeutschland.tv/rostock-griffins/gfl2-rostock-griffins-vs-elmshorn-fighting-pirates', + 'url': 'https://sporteurope.tv/rostock-griffins/gfl2-rostock-griffins-vs-elmshorn-fighting-pirates', 'md5': '35c11a19395c938cdd076b93bda54cde', 'info_dict': { 'id': '9f27a97d-1544-4d0b-aa03-48d92d17a03a', @@ -19,9 +20,9 @@ class SportDeutschlandIE(InfoExtractor): 'title': 'GFL2: Rostock Griffins vs. Elmshorn Fighting Pirates', 'display_id': 'rostock-griffins/gfl2-rostock-griffins-vs-elmshorn-fighting-pirates', 'channel': 'Rostock Griffins', - 'channel_url': 'https://sportdeutschland.tv/rostock-griffins', + 'channel_url': 'https://sporteurope.tv/rostock-griffins', 'live_status': 'was_live', - 'description': 'md5:60cb00067e55dafa27b0933a43d72862', + 'description': r're:Video-Livestream des Spiels Rostock Griffins vs\. Elmshorn Fighting Pirates.+', 'channel_id': '9635f21c-3f67-4584-9ce4-796e9a47276b', 'timestamp': 1749913117, 'upload_date': '20250614', @@ -29,16 +30,16 @@ class SportDeutschlandIE(InfoExtractor): }, }, { # Single-part video, embedded player link - 'url': 'https://player.sportdeutschland.tv/9e9619c4-7d77-43c4-926d-49fb57dc06dc', + 'url': 'https://player.sporteurope.tv/9e9619c4-7d77-43c4-926d-49fb57dc06dc', 'info_dict': { 'id': '9f27a97d-1544-4d0b-aa03-48d92d17a03a', 'ext': 'mp4', 'title': 'GFL2: Rostock Griffins vs. Elmshorn Fighting Pirates', 'display_id': '9e9619c4-7d77-43c4-926d-49fb57dc06dc', 'channel': 'Rostock Griffins', - 'channel_url': 'https://sportdeutschland.tv/rostock-griffins', + 'channel_url': 'https://sporteurope.tv/rostock-griffins', 'live_status': 'was_live', - 'description': 'md5:60cb00067e55dafa27b0933a43d72862', + 'description': r're:Video-Livestream des Spiels Rostock Griffins vs\. Elmshorn Fighting Pirates.+', 'channel_id': '9635f21c-3f67-4584-9ce4-796e9a47276b', 'timestamp': 1749913117, 'upload_date': '20250614', @@ -47,7 +48,7 @@ class SportDeutschlandIE(InfoExtractor): 'params': {'skip_download': True}, }, { # Multi-part video - 'url': 'https://sportdeutschland.tv/rhine-ruhr-2025-fisu-world-university-games/volleyball-w-japan-vs-brasilien-halbfinale-2', + 'url': 'https://sporteurope.tv/rhine-ruhr-2025-fisu-world-university-games/volleyball-w-japan-vs-brasilien-halbfinale-2', 'info_dict': { 'id': '9f63d737-2444-4e3a-a1ea-840df73fd481', 'display_id': 'rhine-ruhr-2025-fisu-world-university-games/volleyball-w-japan-vs-brasilien-halbfinale-2', @@ -55,7 +56,7 @@ class SportDeutschlandIE(InfoExtractor): 'description': 'md5:0a17da15e48a687e6019639c3452572b', 'channel': 'Rhine-Ruhr 2025 FISU World University Games', 'channel_id': '9f5216be-a49d-470b-9a30-4fe9df993334', - 'channel_url': 'https://sportdeutschland.tv/rhine-ruhr-2025-fisu-world-university-games', + 'channel_url': 'https://sporteurope.tv/rhine-ruhr-2025-fisu-world-university-games', 'live_status': 'was_live', }, 'playlist_count': 2, @@ -66,7 +67,7 @@ class SportDeutschlandIE(InfoExtractor): 'title': 'Volleyball w: Japan vs. Braslien - Halbfinale 2 Part 1', 'channel': 'Rhine-Ruhr 2025 FISU World University Games', 'channel_id': '9f5216be-a49d-470b-9a30-4fe9df993334', - 'channel_url': 'https://sportdeutschland.tv/rhine-ruhr-2025-fisu-world-university-games', + 'channel_url': 'https://sporteurope.tv/rhine-ruhr-2025-fisu-world-university-games', 'duration': 14773.0, 'timestamp': 1753085197, 'upload_date': '20250721', @@ -79,16 +80,17 @@ class SportDeutschlandIE(InfoExtractor): 'title': 'Volleyball w: Japan vs. Braslien - Halbfinale 2 Part 2', 'channel': 'Rhine-Ruhr 2025 FISU World University Games', 'channel_id': '9f5216be-a49d-470b-9a30-4fe9df993334', - 'channel_url': 'https://sportdeutschland.tv/rhine-ruhr-2025-fisu-world-university-games', + 'channel_url': 'https://sporteurope.tv/rhine-ruhr-2025-fisu-world-university-games', 'duration': 14773.0, 'timestamp': 1753128421, 'upload_date': '20250721', 'live_status': 'was_live', }, }], + 'skip': '404 Not Found', }, { # Livestream - 'url': 'https://sportdeutschland.tv/dtb/gymnastik-international-tag-1', + 'url': 'https://sporteurope.tv/dtb/gymnastik-international-tag-1', 'info_dict': { 'id': '95d71b8a-370a-4b87-ad16-94680da18528', 'ext': 'mp4', @@ -96,7 +98,7 @@ class SportDeutschlandIE(InfoExtractor): 'display_id': 'dtb/gymnastik-international-tag-1', 'channel_id': '936ecef1-2f4a-4e08-be2f-68073cb7ecab', 'channel': 'Deutscher Turner-Bund', - 'channel_url': 'https://sportdeutschland.tv/dtb', + 'channel_url': 'https://sporteurope.tv/dtb', 'description': 'md5:07a885dde5838a6f0796ee21dc3b0c52', 'live_status': 'is_live', }, @@ -106,9 +108,9 @@ class SportDeutschlandIE(InfoExtractor): def _process_video(self, asset_id, video): is_live = video['type'] == 'mux_live' token = self._download_json( - f'https://api.sportdeutschland.tv/api/web/personal/asset-token/{asset_id}', + f'https://api.sporteurope.tv/api/web/personal/asset-token/{asset_id}', video['id'], query={'type': video['type'], 'playback_id': video['src']}, - headers={'Referer': 'https://sportdeutschland.tv/'})['token'] + headers={'Referer': 'https://sporteurope.tv/'})['token'] formats, subtitles = self._extract_m3u8_formats_and_subtitles( f'https://stream.mux.com/{video["src"]}.m3u8?token={token}', video['id'], live=is_live) @@ -126,7 +128,7 @@ class SportDeutschlandIE(InfoExtractor): def _real_extract(self, url): display_id = self._match_id(url) meta = self._download_json( - f'https://api.sportdeutschland.tv/api/stateless/frontend/assets/{display_id}', + f'https://api.sporteurope.tv/api/stateless/frontend/assets/{display_id}', display_id, query={'access_token': 'true'}) info = { @@ -139,7 +141,7 @@ class SportDeutschlandIE(InfoExtractor): 'channel_id': ('profile', 'id'), 'is_live': 'currently_live', 'was_live': 'was_live', - 'channel_url': ('profile', 'slug', {lambda x: f'https://sportdeutschland.tv/{x}'}), + 'channel_url': ('profile', 'slug', {lambda x: f'https://sporteurope.tv/{x}'}), }, get_all=False), } diff --git a/yt-dlp/yt_dlp/extractor/xhamster.py b/yt-dlp/yt_dlp/extractor/xhamster.py index d83d9da9f6..8b2893b07f 100644 --- a/yt-dlp/yt_dlp/extractor/xhamster.py +++ b/yt-dlp/yt_dlp/extractor/xhamster.py @@ -1,8 +1,6 @@ -import base64 -import codecs import itertools import re -import string +import urllib.parse from .common import InfoExtractor from ..utils import ( @@ -16,7 +14,6 @@ from ..utils import ( join_nonempty, parse_duration, str_or_none, - try_call, try_get, unified_strdate, url_or_none, @@ -32,7 +29,7 @@ class _ByteGenerator: try: self._algorithm = getattr(self, f'_algo{algo_id}') except AttributeError: - raise ExtractorError(f'Unknown algorithm ID: {algo_id}') + raise ExtractorError(f'Unknown algorithm ID "{algo_id}"') self._s = to_signed_32(seed) def _algo1(self, s): @@ -216,32 +213,28 @@ class XHamsterIE(InfoExtractor): 'only_matching': True, }] - _XOR_KEY = b'xh7999' - def _decipher_format_url(self, format_url, format_id): - if all(char in string.hexdigits for char in format_url): - byte_data = bytes.fromhex(format_url) - seed = int.from_bytes(byte_data[1:5], byteorder='little', signed=True) - byte_gen = _ByteGenerator(byte_data[0], seed) - return bytearray(byte ^ next(byte_gen) for byte in byte_data[5:]).decode('latin-1') + parsed_url = urllib.parse.urlparse(format_url) - cipher_type, _, ciphertext = try_call( - lambda: base64.b64decode(format_url).decode().partition('_')) or [None] * 3 - - if not cipher_type or not ciphertext: - self.report_warning(f'Skipping format "{format_id}": failed to decipher URL') + hex_string, path_remainder = self._search_regex( + r'^/(?P[0-9a-fA-F]{12,})(?P[/,].+)$', parsed_url.path, 'url components', + default=(None, None), group=('hex', 'rem')) + if not hex_string: + self.report_warning(f'Skipping format "{format_id}": unsupported URL format') return None - if cipher_type == 'xor': - return bytes( - a ^ b for a, b in - zip(ciphertext.encode(), itertools.cycle(self._XOR_KEY))).decode() + byte_data = bytes.fromhex(hex_string) + seed = int.from_bytes(byte_data[1:5], byteorder='little', signed=True) - if cipher_type == 'rot13': - return codecs.decode(ciphertext, cipher_type) + try: + byte_gen = _ByteGenerator(byte_data[0], seed) + except ExtractorError as e: + self.report_warning(f'Skipping format "{format_id}": {e.msg}') + return None - self.report_warning(f'Skipping format "{format_id}": unsupported cipher type "{cipher_type}"') - return None + deciphered = bytearray(byte ^ next(byte_gen) for byte in byte_data[5:]).decode('latin-1') + + return parsed_url._replace(path=f'/{deciphered}{path_remainder}').geturl() def _fixup_formats(self, formats): for f in formats: @@ -364,8 +357,11 @@ class XHamsterIE(InfoExtractor): 'height': get_height(quality), 'filesize': format_sizes.get(quality), 'http_headers': { - 'Referer': standard_url, + 'Referer': urlh.url, }, + # HTTP formats return "Wrong key" error even when deciphered by site JS + # TODO: Remove this when resolved on the site's end + '__needs_testing': True, }) categories_list = video.get('categories') @@ -402,7 +398,8 @@ class XHamsterIE(InfoExtractor): 'age_limit': age_limit if age_limit is not None else 18, 'categories': categories, 'formats': self._fixup_formats(formats), - '_format_sort_fields': ('res', 'proto', 'tbr'), + # TODO: Revert to ('res', 'proto', 'tbr') when HTTP formats problem is resolved + '_format_sort_fields': ('res', 'proto:m3u8', 'tbr'), } # Old layout fallback