diff --git a/clashoo/Makefile b/clashoo/Makefile new file mode 100644 index 00000000..a74e6673 --- /dev/null +++ b/clashoo/Makefile @@ -0,0 +1,156 @@ +include $(TOPDIR)/rules.mk + +PKG_NAME:=clashoo +PKG_UPSTREAM_VERSION:=1.19.33 +PKG_UPSTREAM_TAG:=v$(PKG_UPSTREAM_VERSION) +PKG_SHORT_SHA:=3f27ef3 + +PKG_VERSION:=2026.10.25.$(PKG_UPSTREAM_VERSION) +PKG_RELEASE:=5 + +PKG_SOURCE:=$(PKG_NAME)-stable-$(PKG_UPSTREAM_TAG)-$(PKG_SHORT_SHA).tar.gz +PKG_SOURCE_SUBDIR:=$(PKG_NAME)-stable-$(PKG_UPSTREAM_TAG)-$(PKG_SHORT_SHA) +PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_SOURCE_SUBDIR) +PKG_SOURCE_URL:=https://github.com/kenzok8/openwrt-clashoo/releases/download/mihomo-src +PKG_SOURCE_VERSION:=3f27ef39f8c96bbfbc8b0b9bb10f5c9ef60149d8 +PKG_HASH:=1ef7a4087f3805bd2624a940e2e43a0bc23719a25465c9f503aa6c11572f1321 +PKG_BUILD_VERSION:=$(PKG_UPSTREAM_TAG) + +PKG_LICENSE:=GPL3.0+ +PKG_MAINTAINER:=kenzok8 + +PKG_BUILD_DEPENDS:=golang/host +PKG_BUILD_PARALLEL:=1 +PKG_BUILD_FLAGS:=no-mips16 + +PKG_BUILD_TIME:=$(shell date -u -Iseconds) + +GO_PKG:=github.com/metacubex/mihomo +GO_PKG_LDFLAGS_X:=$(GO_PKG)/constant.Version=$(PKG_BUILD_VERSION) $(GO_PKG)/constant.BuildTime=$(PKG_BUILD_TIME) +GO_PKG_TAGS:=with_gvisor + +include $(INCLUDE_DIR)/package.mk +include $(TOPDIR)/feeds/packages/lang/golang/golang-package.mk + +define Package/clashoo + SECTION:=net + CATEGORY:=Network + TITLE:=Clashoo (Mihomo) + DEPENDS:=$(GO_ARCH_DEPENDS) +ca-bundle +yq +firewall4 +ip-full +coreutils-nohup +coreutils-timeout +kmod-inet-diag +kmod-nft-socket +kmod-nft-tproxy +kmod-tun +kmod-dummy +unzip + PROVIDES:=mihomo clash-meta +endef + +define Package/clashoo/description + Mihomo proxy core with runtime environment for Clashoo. +endef + +define Package/clashoo/conffiles +/etc/config/clashoo +endef + +define Package/clashoo/install + $(call GoPackage/Package/Install/Bin,$(1)) + + $(INSTALL_DIR) $(1)/usr/libexec/clashoo + $(INSTALL_DIR) $(1)/usr/bin + mv -f $(1)/usr/bin/mihomo $(1)/usr/libexec/clashoo/mihomo-stable + ln -sf /usr/libexec/clashoo/mihomo-stable $(1)/usr/bin/mihomo + ln -sf /usr/libexec/clashoo/mihomo-stable $(1)/usr/bin/clash-meta + + $(INSTALL_DIR) $(1)/etc/init.d + $(INSTALL_DIR) $(1)/etc/config + $(INSTALL_DIR) $(1)/etc/clashoo + $(INSTALL_DIR) $(1)/etc/clashoo/provider + $(INSTALL_DIR) $(1)/etc/clashoo/proxyprovider + $(INSTALL_DIR) $(1)/etc/clashoo/ruleprovider + $(INSTALL_DIR) $(1)/usr/share/clashoo + $(INSTALL_DIR) $(1)/usr/share/clashbackup + $(INSTALL_DIR) $(1)/usr/share/clashoo/config + $(INSTALL_DIR) $(1)/usr/share/clashoo/config/sub + $(INSTALL_DIR) $(1)/usr/share/clashoo/config/upload + $(INSTALL_DIR) $(1)/usr/share/clashoo/config/custom + + $(INSTALL_BIN) $(CURDIR)/files/etc/init.d/clashoo $(1)/etc/init.d/clashoo + $(INSTALL_CONF) $(CURDIR)/files/etc/config/clashoo $(1)/etc/config/clashoo + $(INSTALL_DATA) $(CURDIR)/files/etc/config/clashoo $(1)/usr/share/clashoo/clashoo.default + $(CP) $(CURDIR)/files/usr/share/clashoo/* $(1)/usr/share/clashoo/ + $(INSTALL_BIN) $(CURDIR)/files/usr/share/clashoo/migrate_lan_acl.sh $(1)/usr/share/clashoo/migrate_lan_acl.sh + $(INSTALL_BIN) $(CURDIR)/files/usr/share/clashoo/migrate_fake_ip_filter.sh $(1)/usr/share/clashoo/migrate_fake_ip_filter.sh + $(INSTALL_BIN) $(CURDIR)/files/usr/share/clashoo/detect_core_arch.sh $(1)/usr/share/clashoo/detect_core_arch.sh + $(INSTALL_BIN) $(CURDIR)/files/usr/share/clashoo/diagnose_bypass.sh $(1)/usr/share/clashoo/diagnose_bypass.sh + + $(RM) $(1)/usr/share/clashoo/dashboard + $(RM) $(1)/usr/share/clashoo/yacd + $(RM) $(1)/usr/share/clashoo/update.log + $(RM) $(1)/usr/share/clashoo/geoip.log + $(RM) $(1)/usr/share/clashoo/core_down_complete +endef + +define Package/clashoo/preinst +#!/bin/sh +if [ -z "$${IPKG_INSTROOT}" ]; then + save_core() { + src="$$1" + dst="$$2" + [ -f "$$src" ] && [ ! -L "$$src" ] || return 0 + if [ -e "$$dst" ] || [ -L "$$dst" ]; then return 0; fi + mkdir -p "$$(dirname "$$dst")" || return 1 + tmp="$$dst.tmp.$$$$" + cp "$$src" "$$tmp" && chmod 0755 "$$tmp" && cmp -s "$$src" "$$tmp" || { + rm -f "$$tmp" + return 1 + } + if command -v sha256sum >/dev/null 2>&1; then + old_hash="$$(sha256sum "$$src" | awk '{print $$1}')" + new_hash="$$(sha256sum "$$tmp" | awk '{print $$1}')" + [ "$$old_hash" = "$$new_hash" ] || { + rm -f "$$tmp" + return 1 + } + fi + mv -f "$$tmp" "$$dst" + } + save_core /usr/bin/mihomo /usr/share/clashoo/bin/mihomo-alpha || exit 1 + save_core /usr/bin/mihomo-stable /usr/share/clashoo/bin/mihomo-stable || exit 1 + if [ -x /usr/bin/clash-meta ] && [ ! -L /usr/bin/clash-meta ] && [ ! -e /usr/share/clashoo/bin/mihomo-stable ]; then + if ! /usr/bin/clash-meta -v 2>/dev/null | grep -Eiq '(alpha|beta|rc|pre|smart)'; then + save_core /usr/bin/clash-meta /usr/share/clashoo/bin/mihomo-stable || exit 1 + fi + fi +fi +exit 0 +endef + +define Package/clashoo/postinst +#!/bin/sh +if [ -z "$${IPKG_INSTROOT}" ]; then + rm -f /usr/share/clashoo/check_luci_version.sh /usr/share/clashoo/check_clash_meta_version.sh /usr/share/clashoo/check_mihomo_core_version.sh + rm -f /usr/share/clashoo/cuslist.sh /usr/share/clashoo/groups.sh /usr/share/clashoo/load_groups.sh /usr/share/clashoo/panel_diag.sh + rm -f /usr/share/clashoo/rmlist.sh /usr/share/clashoo/uplist.sh /usr/share/clashoo/rpc_restart_async.sh /usr/share/clashoo/rpc_update_china_ip_async.sh + rm -rf /usr/share/clashoo/create + # 旧方案把 stable 内核放在 /usr/bin/mihomo-stable,现方案改用 + # /usr/libexec/clashoo/mihomo-stable(随包)+ /usr/share/clashoo/bin/(自定义下载)。 + # preinst 已把它抄到 /usr/share/clashoo/bin/mihomo-stable,确认内容相同后才删 + # 旧位置;目标已有其他自定义内核时保留旧文件,避免误删。 + if [ -f /usr/share/clashoo/bin/mihomo-stable ] && [ -f /usr/bin/mihomo-stable ] && [ ! -L /usr/bin/mihomo-stable ] && cmp -s /usr/bin/mihomo-stable /usr/share/clashoo/bin/mihomo-stable; then + rm -f /usr/bin/mihomo-stable + fi + /usr/share/clashoo/migrate_lan_acl.sh >/dev/null 2>&1 || true + /usr/share/clashoo/migrate_fake_ip_filter.sh >/dev/null 2>&1 || true + /usr/share/clashoo/detect_core_arch.sh >/dev/null 2>&1 || true + if [ "$$(uci -q get clashoo.config.enable 2>/dev/null)" = "1" ]; then + /etc/init.d/clashoo enable 2>/dev/null || true + else + /etc/init.d/clashoo disable 2>/dev/null || true + fi +fi +exit 0 +endef + +define Build/Prepare + $(Build/Prepare/Default) + $(RM) -r $(PKG_BUILD_DIR)/rules/logic_test +endef + +$(eval $(call GoBinPackage,clashoo)) +$(eval $(call BuildPackage,clashoo)) diff --git a/clashoo/files/etc/config/clashoo b/clashoo/files/etc/config/clashoo new file mode 100644 index 00000000..d744fb10 --- /dev/null +++ b/clashoo/files/etc/config/clashoo @@ -0,0 +1,156 @@ + +config clashoo 'config' + option redir_port '7891' + option http_port '8080' + option socks_port '1080' + option mixed_port '7890' + option enable_ipv6 'true' + option enable '0' + option dash_port '9090' + option dashboard_panel 'zashboard' + option level 'info' + option core '2' + option allow_lan '1' + option dnsforwader '1' + option subcri 'meta' + option cusrule '0' + option dnscache '1' + option p_mode 'rule' + option append_rules '0' + option enhanced_mode 'fake-ip' + option fake_ip_range '198.18.0.1/16' + option fake_ip_range6 'fc00::/18' + list fake_ip_filter '+.lan' + list fake_ip_filter '+.local' + list fake_ip_filter 'localhost.ptlogin2.qq.com' + list fake_ip_filter 'rule-set:cn_domain' + option fake_ip_filter_migrated '1' + option listen_port '1053' + option interf '0' + option download_core 'amd64-compatible' + option dcore '2' + option same_tag '1' + option geoip_source '3' + option geoip_format 'mmdb' + option geodata_loader 'standard' + option tproxy_port '7982' + option access_control '0' + option acl_migrated '1' + option tcp_mode 'redirect' + option tun_mode '0' + option core_mirror_prefix 'https://gh-proxy.com/' + option core_download_advanced '0' + list default_nameserver '223.5.5.5' + list default_nameserver '119.29.29.29' + option dns_ecs '' + option dns_ecs_override '0' + option fallback_filter_geoip '0' + list fallback_filter_ipcidr '240.0.0.0/4' + option dns_respect_rules '1' + option dns_loopback_compat '0' + option dns_migrated '1' + option block_quic '0' + option singbox_independent_cache '0' + option udp_mode 'tproxy' + option ipv4_dns_hijack '1' + option ipv6_dns_hijack '1' + option ipv4_proxy '1' + option ipv6_proxy '1' + option fake_ip_ping_hijack '1' + option dns_leak_protect '0' + option geoip_mmdb_url 'https://raw.githubusercontent.com/Loyalsoldier/geoip/release/Country.mmdb' + option geosite_url 'https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geosite.dat' + option geoip_dat_url 'https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.dat' + option enable_udp '1' + option stack 'gvisor' + option bypass_china '1' + option bypass_china_ipv6 '1' + option bypass_port_mode 'common' + option sniffer_streaming '1' + option selection_cache '1' + option fake_ip_cache '1' + option enable_dns '1' + option disable_quic_gso '1' + option smart_policy_priority 'Premium:0.9;SG:1.3' + option smart_prefer_asn '1' + option smart_uselightgbm '1' + option smart_collectdata '1' + option smart_collect_size '100' + option smart_collect_rate '1' + option smart_lgbm_auto_update '1' + option smart_lgbm_update_interval '72' + option smart_lgbm_url 'https://github.com/vernesong/mihomo/releases/download/LightGBM-Model/Model.bin' + list defaul_nameserver '114.114.114.114' + list defaul_nameserver '223.5.5.5' + option auto_update '1' + option auto_update_time '72' + option auto_subscription_update '0' + option subscription_update_interval '72' + option auto_clear_log '1' + option clear_time '72' + option core_type 'mihomo' + option smart_auto_switch '0' + option core_only '0' + +config hosts + option enabled '0' + option adress '*.clash.dev' + option ip '127.0.0.1' + +config hosts + option enabled '0' + option adress 'alpha.clash.dev' + option ip '::1' + +config dnsservers + option enabled '1' + option ser_type 'nameserver' + option ser_address 'https://dns.alidns.com/dns-query' + option protocol 'none' + +config dnsservers + option enabled '1' + option ser_type 'nameserver' + option ser_address 'https://doh.pub/dns-query' + option protocol 'none' + +config dnsservers + option enabled '1' + option ser_type 'proxy-server-nameserver' + option ser_address '1.1.1.1' + option protocol 'tls://' + option ser_port '853' + +config dnsservers + option enabled '1' + option ser_type 'fallback' + option ser_address 'https://cloudflare-dns.com/dns-query' + option protocol 'none' + +config dnsservers + option enabled '1' + option ser_type 'fallback' + option ser_address 'https://dns.google/dns-query' + option protocol 'none' + +config dnsservers + option enabled '1' + option ser_type 'direct-nameserver' + option ser_address '223.5.5.5' + option protocol 'udp://' + +config dns_policy + option enabled '1' + option policy_type 'nameserver-policy' + option matcher 'geosite:cn' + list nameserver 'https://dns.alidns.com/dns-query' + list nameserver 'https://doh.pub/dns-query' + +config authentication + +# default empty custom-rule row so the section shows the input form, not just an add +# button; injectors skip rules with an empty ipaaddr, so it generates nothing until filled +config addtype + option type 'DOMAIN-SUFFIX' + option pgroup 'DIRECT' + option ipaaddr '' diff --git a/clashoo/files/etc/init.d/clashoo b/clashoo/files/etc/init.d/clashoo new file mode 100755 index 00000000..a58f650e --- /dev/null +++ b/clashoo/files/etc/init.d/clashoo @@ -0,0 +1,2769 @@ +#!/bin/sh /etc/rc.common +USE_PROCD=1 +EXTRA_COMMANDS="refresh_cron" + +START=99 +STOP=15 + +CORE_LOG_PATH="/var/log/clashoo/core.log" +SINGBOX_BIN_DIR="/usr/share/clashoo/bin" +SINGBOX_RUNTIME_DIR="/var/run/clashoo/sing-box" +SINGBOX_RUNTIME_CONFIG="$SINGBOX_RUNTIME_DIR/config.json" +SINGBOX_WORKDIR="$SINGBOX_RUNTIME_DIR/work" + +. /usr/share/clashoo/runtime/dns_helpers.sh 2>/dev/null || true + +command_exists() { + command -v "$1" >/dev/null 2>&1 +} + +shell_quote() { + printf "'" + printf '%s' "$1" | sed "s/'/'\\\\''/g" + printf "'" +} + +use_fw4_backend() { + command_exists fw4 && command_exists nft +} + +backend_name() { + if use_fw4_backend; then + echo "fw4" + else + echo "unsupported" + fi +} + +backend_apply_rules() { + if use_fw4_backend; then + /usr/share/clashoo/net/fw4.sh apply + else + _log_runtime_error "Current environment requires fw4+nft support" + return 1 + fi +} + +backend_remove_rules() { + if use_fw4_backend; then + /usr/share/clashoo/net/fw4.sh remove + else + return 0 + fi +} + +_clashoo_enabled() { + [ "$(uci -q get clashoo.config.enable 2>/dev/null)" = "1" ] +} + +# Compute start-wait window: TUN needs longer for utun setup + fake-ip pool init. +# Override via `uci set clashoo.config.start_wait_secs=N` for slow devices. +_compute_start_wait() { + local override + override="$(uci -q get clashoo.config.start_wait_secs)" + if [ -n "$override" ] && [ "$override" -gt 0 ] 2>/dev/null; then + echo "$override"; return + fi + if [ "$(uci -q get clashoo.config.tun_mode)" = "1" ]; then + echo 15 + else + echo 6 + fi +} + +selected_singbox_binary() { + local dcore + dcore="$(uci -q get clashoo.config.dcore 2>/dev/null)" + case "$dcore" in + 4) echo "$SINGBOX_BIN_DIR/sing-box-stable" ;; + 5) echo "$SINGBOX_BIN_DIR/sing-box-alpha" ;; + *) return 1 ;; + esac +} + +_migrate_legacy_singbox_binary() { + local dcore target legacy staged + dcore="$(uci -q get clashoo.config.dcore 2>/dev/null)" + target="$(selected_singbox_binary)" || return 1 + [ -e "$target" ] && return 0 + case "$dcore" in + 4) legacy="/usr/bin/sing-box-stable" ;; + 5) legacy="/usr/bin/sing-box-alpha" ;; + *) return 1 ;; + esac + [ -x "$legacy" ] || return 1 + mkdir -p "$SINGBOX_BIN_DIR" >/dev/null 2>&1 || return 1 + staged="${target}.$$" + rm -f "$staged" >/dev/null 2>&1 + cp -f "$legacy" "$staged" >/dev/null 2>&1 || { + rm -f "$staged" >/dev/null 2>&1 + return 1 + } + chmod 0755 "$staged" >/dev/null 2>&1 || { + rm -f "$staged" >/dev/null 2>&1 + return 1 + } + mv -f "$staged" "$target" >/dev/null 2>&1 || { + rm -f "$staged" >/dev/null 2>&1 + return 1 + } + return 0 +} + +_ensure_singbox_binary() { + local target + target="$(selected_singbox_binary)" || return 1 + [ -e "$target" ] || _migrate_legacy_singbox_binary || return 1 + [ -x "$target" ] +} + +_singbox_pid_owns_private_binary() { + local pid stable alpha exe cmdline + pid="$1" + stable="$SINGBOX_BIN_DIR/sing-box-stable" + alpha="$SINGBOX_BIN_DIR/sing-box-alpha" + [ -d "/proc/$pid" ] || return 1 + if command_exists readlink; then + exe="$(readlink -f "/proc/$pid/exe" 2>/dev/null)" + if [ "$exe" = "$stable" ] || [ "$exe" = "$alpha" ]; then + return 0 + fi + fi + cmdline="$(tr '\000' ' ' < "/proc/$pid/cmdline" 2>/dev/null)" + case "$cmdline" in + "$stable"|"$stable "*|"$alpha"|"$alpha "*) return 0 ;; + esac + return 1 +} + +_singbox_instance_pid() { + local _pid + _pid="$(ubus call service list '{"name":"clashoo","verbose":true}' 2>/dev/null \ + | jsonfilter -e '@.clashoo.instances.singbox.pid' 2>/dev/null | head -n1)" + [ -n "$_pid" ] && _singbox_pid_owns_private_binary "$_pid" && echo "$_pid" +} + +_singbox_running() { + local _pid + _pid="$(_singbox_instance_pid)" + [ -n "$_pid" ] && [ -d "/proc/$_pid" ] +} + +# Roll back partial state when start failed after backend rules / DNS were applied. +# Reuses the same cleanup primitives as stop_service so behavior stays consistent. +_rollback_failed_start() { + log_msg "Rolling back failed start" "启动失败,正在回退防火墙与 DNS 配置" + backend_remove_rules >/dev/null 2>&1 + cleanup_stale_core_nft >/dev/null 2>&1 + revert_dns >/dev/null 2>&1 + stop_singbox_service >/dev/null 2>&1 + _kill_core >/dev/null 2>&1 + rm -rf /www/luci-static/yacd 2>/dev/null + del_cron >/dev/null 2>&1 +} + +cleanup_stale_core_nft() { + for _nft_table in "inet mihomo" "inet sing-box"; do + nft delete table $_nft_table 2>/dev/null || true + done +} + +_core_candidates() { + echo "/usr/share/clashoo/bin/mihomo-stable /usr/libexec/clashoo/mihomo-stable /usr/share/clashoo/bin/mihomo-alpha /usr/bin/mihomo /usr/bin/clash-meta /etc/clashoo/clash" +} + +_migrate_legacy_mihomo_stable() { + local legacy=/usr/bin/clash-meta target=/usr/share/clashoo/bin/mihomo-stable version staged + [ -x "$target" ] && return 0 + [ -x "$legacy" ] && [ ! -L "$legacy" ] || return 0 + version=$("$legacy" -v 2>/dev/null | head -n 1) + [ -n "$version" ] || return 0 + echo "$version" | grep -Eiq '(alpha|beta|rc|pre|smart)' && return 0 + mkdir -p "$(dirname "$target")" || return 1 + staged="${target}.$$" + cp -f "$legacy" "$staged" 2>/dev/null && chmod 0755 "$staged" && mv -f "$staged" "$target" +} + +_mihomo_stable_binary() { + local bundled=/usr/libexec/clashoo/mihomo-stable manual=/usr/share/clashoo/bin/mihomo-stable bundled_ver manual_ver + # A successful custom download is an explicit override, even if its version is older. + if [ -x "$manual" ] && + [ "$(sed -n '1{s/^[[:space:]]*//;s/[[:space:]]*$//;p;}' /usr/share/clashoo/clash_meta_version 2>/dev/null)" = "custom-url" ]; then + echo "$manual" + return 0 + fi + [ -x "$bundled" ] || { [ -x "$manual" ] && { echo "$manual"; return 0; }; return 1; } + [ -x "$manual" ] || { echo "$bundled"; return; } + bundled_ver="$("$bundled" -v 2>/dev/null | awk '{for (i = 1; i < NF; i++) if ($i == "Meta") {print $(i + 1); exit}}')" + manual_ver="$("$manual" -v 2>/dev/null | awk '{for (i = 1; i < NF; i++) if ($i == "Meta") {print $(i + 1); exit}}')" + bundled_ver="${bundled_ver#v}" + manual_ver="${manual_ver#v}" + printf '%s\n' "$bundled_ver" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$' || bundled_ver="" + printf '%s\n' "$manual_ver" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$' || manual_ver="" + if [ -n "$bundled_ver" ] && [ -n "$manual_ver" ]; then + if awk -v manual="$manual_ver" -v bundled="$bundled_ver" 'BEGIN { + split(manual, m, "."); split(bundled, b, "."); + for (i = 1; i <= 3; i++) { + if (m[i] + 0 > b[i] + 0) exit 0; + if (m[i] + 0 < b[i] + 0) exit 1; + } + exit 0; + }'; then + echo "$manual" + else + echo "$bundled" + fi + elif [ -n "$manual_ver" ]; then + echo "$manual" + else + echo "$bundled" + fi +} + +_detect_selected_core() { + local selected + selected=$(uci get clashoo.config.core 2>/dev/null) + case "$selected" in + 1) + for _bin in /usr/bin/smart /etc/clashoo/clash /usr/bin/clash; do + [ -x "$_bin" ] && echo "$_bin" && return + done + return 1 + ;; + 2|mihomo) + _mihomo_stable_binary + return $? + ;; + 3|mihomo-alpha) + [ -x /usr/share/clashoo/bin/mihomo-alpha ] && echo /usr/share/clashoo/bin/mihomo-alpha && return + return 1 + ;; + esac + _detect_core +} + +core_supports_advanced_features() { + case "$CLASH" in + */mihomo|*/mihomo-stable|*/mihomo-alpha|*/clash-meta|*/smart) + return 0 + ;; + *) + return 1 + ;; + esac +} + +# only trust procd instance PIDs (not binary name); openclash/nikki/passwall share +# the same binary names. Use ubus service list as source of truth. +_clashoo_instance_pid() { + ubus call service list "{\"name\":\"clashoo\",\"verbose\":true}" 2>/dev/null \ + | jsonfilter -e '@.clashoo.instances.*.pid' 2>/dev/null | head -n1 +} + +_core_running() { + local _pid + _pid="$(_clashoo_instance_pid)" + [ -n "$_pid" ] && [ -d "/proc/$_pid" ] +} + +# kill only clashoo procd instances, never by binary name — would hit co-installed plugins. +_kill_core() { + local _pid _wait + _pid="$(_clashoo_instance_pid)" + [ -n "$_pid" ] || return 0 + kill "$_pid" >/dev/null 2>&1 + _wait=0 + while [ "$_wait" -lt 3 ] && [ -d "/proc/$_pid" ]; do + sleep 1 + _wait=$((_wait + 1)) + done + [ -d "/proc/$_pid" ] && kill -9 "$_pid" >/dev/null 2>&1 +} + +_selected_core_running() { + if is_singbox_selected; then + _singbox_running + else + _core_running + fi +} + +selected_core_family() { + local core_type + core_type="$(uci -q get clashoo.config.core_type 2>/dev/null)" + [ "$core_type" = "singbox" ] && echo "singbox" || echo "mihomo" +} + +is_singbox_selected() { + [ "$(selected_core_family)" = "singbox" ] +} + +sync_legacy_core_field() { + local dcore legacy + dcore="$(uci -q get clashoo.config.dcore 2>/dev/null)" + if is_singbox_selected; then + [ "$dcore" = "5" ] && legacy="5" || legacy="4" + else + case "$dcore" in + 1) legacy="1" ;; + 3) legacy="3" ;; + *) legacy="2" ;; + esac + fi + uci -q set clashoo.config.core="$legacy" + uci -q commit clashoo >/dev/null 2>&1 +} + +prepare_singbox_runtime() { + local active src redir_port tproxy_port mixed_port dns_port tmp_config + active="$(uci -q get clashoo.config.singbox_active 2>/dev/null)" + src="/usr/share/clashoo/config/singbox/${active}" + tmp_config="${SINGBOX_RUNTIME_CONFIG}.tmp.$$" + + mkdir -p "$SINGBOX_RUNTIME_DIR" "$SINGBOX_WORKDIR" "$(dirname "$CORE_LOG_PATH")" >/dev/null 2>&1 || return 1 + rm -f "$tmp_config" >/dev/null 2>&1 + if [ -n "$active" ] && [ -r "$src" ]; then + cp -f "$src" "$tmp_config" >/dev/null 2>&1 || { + rm -f "$tmp_config" >/dev/null 2>&1 + return 1 + } + elif [ -r "$SINGBOX_RUNTIME_CONFIG" ]; then + cp -f "$SINGBOX_RUNTIME_CONFIG" "$tmp_config" >/dev/null 2>&1 || { + rm -f "$tmp_config" >/dev/null 2>&1 + return 1 + } + else + _log_runtime_error "sing-box config.json is missing; please generate or select a sing-box profile first" + rm -f "$tmp_config" >/dev/null 2>&1 + return 1 + fi + + command -v ucode >/dev/null 2>&1 || { + _log_runtime_error "ucode is required to prepare sing-box runtime config" + rm -f "$tmp_config" >/dev/null 2>&1 + return 1 + } + # Migrate every imported config, including core-only mode, before any + # Clashoo-specific normalization. The temp file is in the runtime dir so + # a failed migration/normalization never replaces the last good config. + ucode /usr/share/clashoo/lib/migrate_singbox.uc "$tmp_config" >/dev/null 2>&1 || { + _log_runtime_error "sing-box runtime config migration failed" + rm -f "$tmp_config" >/dev/null 2>&1 + return 1 + } + + if [ "$(uci -q get clashoo.config.core_only 2>/dev/null)" != "1" ]; then + local has_tun_device dash_port dash_pass + + has_tun_device=1 + runtime_has_tun_device || has_tun_device=0 + redir_port="$(uci -q get clashoo.config.redir_port 2>/dev/null)" + tproxy_port="$(uci -q get clashoo.config.tproxy_port 2>/dev/null)" + mixed_port="$(uci -q get clashoo.config.mixed_port 2>/dev/null)" + dns_port="$(uci -q get clashoo.config.listen_port 2>/dev/null)" + dash_port="$(uci -q get clashoo.config.dash_port 2>/dev/null)" + dash_pass="$(uci -q get clashoo.config.dash_pass 2>/dev/null)" + [ -z "$redir_port" ] && redir_port=7891 + [ -z "$tproxy_port" ] && tproxy_port=7982 + [ -z "$mixed_port" ] && mixed_port=7890 + [ -z "$dns_port" ] && dns_port=1053 + [ -z "$dash_port" ] && dash_port=9090 + # routing_mark 6666 must match fw4.sh CORE_ROUTING_MARK (0x1a0a). + # Never reuse PROXY_FWMARK (0x162) — it would pull egress to lo -> unreachable. + ucode /usr/share/clashoo/lib/normalize_singbox_config.uc \ + "$tmp_config" "$redir_port" "$tproxy_port" "$mixed_port" "$has_tun_device" "6666" "$dns_port" "$dash_port" "$dash_pass" >/dev/null 2>&1 || { + _log_runtime_error "sing-box runtime config normalization failed" + rm -f "$tmp_config" >/dev/null 2>&1 + return 1 + } + fi + + mv -f "$tmp_config" "$SINGBOX_RUNTIME_CONFIG" >/dev/null 2>&1 || { + rm -f "$tmp_config" >/dev/null 2>&1 + return 1 + } + return 0 +} + +start_singbox_service() { + local bin + bin="$(selected_singbox_binary)" || return 1 + if ! _ensure_singbox_binary; then + _log_runtime_error "sing-box core not found, please install the local sing-box binary first" + return 1 + fi + prepare_singbox_runtime || return 1 + # preflight dry-run with watchdog: bail on bad config without touching + # firewall/dnsmasq, and never let a hung check freeze startup/UI. + _run_with_timeout 15 "$bin" -c "$SINGBOX_RUNTIME_CONFIG" check >> "$CORE_LOG_PATH" 2>&1 + _pf_rc=$? + if [ "$_pf_rc" -ne 0 ]; then + _pf_reason="$(_preflight_reason)" + if [ "$_pf_rc" -eq 124 ]; then + log_msg "sing-box config preflight timed out, retry shortly. ${_pf_reason}" "sing-box 配置预检超时,稍后自动重试。${_pf_reason}" + runtime_state_record_health "fail" "preflight:singbox_timeout" + else + log_msg "sing-box config dry-run failed; firewall/DNS untouched. ${_pf_reason}" "sing-box 配置预检失败,未应用防火墙与 DNS 改动。${_pf_reason}" + runtime_state_record_health "fail" "preflight:singbox_dry_run_failed" + fi + return 1 + fi + procd_open_instance "singbox" + procd_set_param command "$bin" run -c "$SINGBOX_RUNTIME_CONFIG" -D "$SINGBOX_WORKDIR" + procd_set_param user root + procd_set_param file "$SINGBOX_RUNTIME_CONFIG" + procd_set_param stdout 1 + procd_set_param stderr 1 + procd_set_param limits core="unlimited" + procd_set_param limits nofile="1000000 1000000" + procd_set_param respawn 3600 5 5 + procd_close_instance +} + +stop_singbox_service() { + local _wait=0 _pid + # procd_kill removes the named instance and disables its respawn; signaling + # the child through ubus would let procd recreate it behind our back. + procd_kill clashoo singbox >/dev/null 2>&1 || true + _pid="$(_singbox_instance_pid)" + [ -n "$_pid" ] || return 0 + while [ "$_wait" -lt 3 ]; do + _pid="$(_singbox_instance_pid)" + [ -z "$_pid" ] && return 0 + _singbox_running || return 0 + sleep 1 + _wait=$((_wait + 1)) + done +} + +_log_runtime_error() { + local message="$1" + echo "$message" > "$REAL_LOG" + echo " $(date "+%Y-%m-%d %H:%M:%S") - $message" >> /usr/share/clashoo/clashoo.txt +} + +# Run a command with a self-watchdog timeout. busybox has no `timeout` binary, +# so poll the backgrounded pid and kill it on overrun. Returns the command's +# exit status, or 124 when the watchdog fired (timed out). +_run_with_timeout() { + local _to="$1"; shift + local _pid _i=0 + "$@" & + _pid=$! + while [ "$_i" -lt "$_to" ]; do + kill -0 "$_pid" 2>/dev/null || { wait "$_pid"; return $?; } + sleep 1 + _i=$((_i + 1)) + done + kill -TERM "$_pid" 2>/dev/null + sleep 1 + kill -KILL "$_pid" 2>/dev/null + wait "$_pid" 2>/dev/null + return 124 +} + +# Pull a concise failure reason out of the core log so users see the real error +# (e.g. "can't download MMDB") instead of only "preflight failed". +_preflight_reason() { + local line + line="$(grep -E 'level=(error|fatal)' "$CORE_LOG_PATH" 2>/dev/null | tail -1)" + [ -n "$line" ] || line="$(tail -1 "$CORE_LOG_PATH" 2>/dev/null)" + case "$line" in + *'msg="'*) line="$(printf '%s' "$line" | sed -n 's/.*msg="\([^"]*\)".*/\1/p')" ;; + esac + printf '%s' "$line" +} + +log_msg() { + local en_msg="$1" zh_msg="$2" + local out_msg now + now="$(date "+%Y-%m-%d %H:%M:%S")" + if [ "${lang}" = "en" ]; then + out_msg="$en_msg" + else + out_msg="$zh_msg" + fi + echo "$out_msg" > "$REAL_LOG" + [ -n "$out_msg" ] && echo " ${now} - ${out_msg}" >> /usr/share/clashoo/clashoo.txt +} + +runtime_state_reset() { + rm -f "$RUNTIME_STATE_FILE" >/dev/null 2>&1 || true +} + +runtime_state_set() { + local key="$1" value="${2:-}" safe_value + [ -n "$key" ] || return 0 + mkdir -p "$(dirname "$RUNTIME_STATE_FILE")" >/dev/null 2>&1 + touch "$RUNTIME_STATE_FILE" + value="$(printf '%s' "$value" | tr '\r\n' ' ')" + safe_value="$(printf '%s' "$value" | sed 's/[\/&|\\]/\\&/g')" + if grep -q "^${key}=" "$RUNTIME_STATE_FILE" 2>/dev/null; then + sed -i "s|^${key}=.*|${key}=${safe_value}|g" "$RUNTIME_STATE_FILE" + else + echo "${key}=${value}" >> "$RUNTIME_STATE_FILE" + fi +} + +runtime_has_tun_device() { + ip tuntap add mode tun name cotuntest >/dev/null 2>&1 || return 1 + ip link del cotuntest >/dev/null 2>&1 || true + return 0 +} + +resolve_effective_tp_modes() { + local configured_tcp configured_udp + configured_tcp="$(uci -q get clashoo.config.tcp_mode 2>/dev/null)" + configured_udp="$(uci -q get clashoo.config.udp_mode 2>/dev/null)" + [ -z "$configured_tcp" ] && configured_tcp="redirect" + [ -z "$configured_udp" ] && configured_udp="$configured_tcp" + + EFFECTIVE_TCP_MODE="$configured_tcp" + EFFECTIVE_UDP_MODE="$configured_udp" + RUNTIME_DEGRADED="0" + RUNTIME_DEGRADE_REASON="" + HAS_TUN_DEVICE="1" + + if ! runtime_has_tun_device; then + HAS_TUN_DEVICE="0" + if [ "$EFFECTIVE_TCP_MODE" = "tun" ]; then + EFFECTIVE_TCP_MODE="redirect" + RUNTIME_DEGRADED="1" + fi + if [ "$EFFECTIVE_UDP_MODE" = "tun" ]; then + EFFECTIVE_UDP_MODE="tproxy" + RUNTIME_DEGRADED="1" + fi + fi + + [ "$RUNTIME_DEGRADED" = "1" ] && RUNTIME_DEGRADE_REASON="tun_unavailable_auto_fallback" +} + +runtime_state_record_preflight() { + local configured_tcp configured_udp core_family enhanced_mode + core_family="$1" + configured_tcp="$(uci -q get clashoo.config.tcp_mode 2>/dev/null)" + configured_udp="$(uci -q get clashoo.config.udp_mode 2>/dev/null)" + enhanced_mode="$(uci -q get clashoo.config.enhanced_mode 2>/dev/null)" + [ -z "$configured_tcp" ] && configured_tcp="redirect" + [ -z "$configured_udp" ] && configured_udp="$configured_tcp" + [ -z "$enhanced_mode" ] && enhanced_mode="fake-ip" + + runtime_state_set "configured_core" "$core_family" + runtime_state_set "configured_tcp_mode" "$configured_tcp" + runtime_state_set "configured_udp_mode" "$configured_udp" + runtime_state_set "effective_tcp_mode" "$EFFECTIVE_TCP_MODE" + runtime_state_set "effective_udp_mode" "$EFFECTIVE_UDP_MODE" + runtime_state_set "has_tun_device" "$HAS_TUN_DEVICE" + runtime_state_set "degraded" "$RUNTIME_DEGRADED" + runtime_state_set "degrade_reason" "$RUNTIME_DEGRADE_REASON" + runtime_state_set "enhanced_mode" "$enhanced_mode" + runtime_state_set "health_status" "pending" + runtime_state_set "health_detail" "init" + runtime_state_set "updated_at" "$(date +%s)" +} + +runtime_state_record_health() { + local status detail + status="$1" + detail="${2:-}" + [ -z "$status" ] && status="unknown" + runtime_state_set "health_status" "$status" + runtime_state_set "health_detail" "$detail" + runtime_state_set "updated_at" "$(date +%s)" +} + +runtime_state_accepts_health_update() { + _selected_core_running || return 1 + case "$(grep '^health_detail=' "$RUNTIME_STATE_FILE" 2>/dev/null | cut -d= -f2-)" in + service_stopped|service_disabled|boot_disabled) + return 1 + ;; + esac + return 0 +} + +curl_has_final_http_response() { + awk ' + /^HTTP\// && $0 !~ /Connection established/ { ok=1 } + END { exit ok ? 0 : 1 } + ' +} + +runtime_run_health_check() { + local dns_state explicit_state transparent_state detail require_fakeip mixed_port _try + local dns_out dns_addr _url + + runtime_state_accepts_health_update || return 1 + + dns_state="na" + explicit_state="na" + transparent_state="na" + require_fakeip="0" + [ "$(uci -q get clashoo.config.enable_dns 2>/dev/null)" != "0" ] && [ "$(uci -q get clashoo.config.enhanced_mode 2>/dev/null)" = "fake-ip" ] && require_fakeip="1" + + if command_exists nslookup; then + dns_state="fail" + for _try in 1 2 3; do + if command_exists timeout; then + dns_out="$(timeout 3 nslookup www.google.com 127.0.0.1 2>/dev/null)" + else + dns_out="$(nslookup www.google.com 127.0.0.1 2>/dev/null)" + fi + dns_addr="$(echo "$dns_out" | awk '/^Address: /{print $2} /^Address [0-9]+: /{print $3}' | grep -E '^[0-9a-fA-F:.]+$' | tail -n1)" + if [ -n "$dns_addr" ]; then + if echo "$dns_addr" | grep -Eq '^198\.18\.|^fc00:'; then + dns_state="ok" + else + dns_state="compat" + fi + break + fi + sleep 1 + done + fi + + mixed_port="$(uci -q get clashoo.config.mixed_port 2>/dev/null)" + [ -z "$mixed_port" ] && mixed_port="7890" + if command_exists curl; then + explicit_state="fail" + for _url in https://www.google.com/generate_204 https://www.gstatic.com/generate_204; do + for _try in 1 2; do + if curl -4 --proxy "http://127.0.0.1:${mixed_port}" -I --max-time 6 -sS "$_url" 2>/dev/null | curl_has_final_http_response; then + explicit_state="ok" + break 2 + fi + sleep 1 + done + done + transparent_state="fail" + for _url in https://www.google.com https://www.gstatic.com/generate_204; do + for _try in 1 2; do + if curl -4 -I --max-time 6 -sS "$_url" 2>/dev/null | curl_has_final_http_response; then + transparent_state="ok" + break 2 + fi + sleep 1 + done + done + fi + + detail="dns:${dns_state};explicit:${explicit_state};transparent:${transparent_state}" + # Pass on explicit (local mixed-port) reachability alone. The transparent + # probe tests the router's OWN output-redirect path, which is unreliable on + # some stacks (PPPoE/GL.iNet, issue #25) yet does not reflect LAN-client + # proxying — keep it informational in `detail`, not a pass gate. + if [ "$explicit_state" = "ok" ]; then + if [ "$require_fakeip" = "0" ] || [ "$dns_state" = "ok" ] || [ "$dns_state" = "compat" ] || [ "$dns_state" = "na" ]; then + runtime_state_accepts_health_update || return 1 + runtime_state_record_health "pass" "$detail" + return 0 + fi + fi + + runtime_state_accepts_health_update || return 1 + runtime_state_record_health "fail" "$detail" + echo " $(date "+%Y-%m-%d %H:%M:%S") - runtime health check failed: ${detail}" >> /usr/share/clashoo/clashoo.txt + return 1 +} + +# check now; on fail detach a bounded re-check so cold-start false-fails self-correct +runtime_run_health_check_settle() { + runtime_run_health_check && return 0 + local lock="/tmp/clashoo_health_recheck.lock" + ( set -C; echo "$$" >"$lock" ) 2>/dev/null || return 1 + ( + exec 1000>&- 2>/dev/null + trap 'rm -f "$lock"' EXIT + _i=0 + while [ "$_i" -lt 8 ]; do + sleep 15 + _selected_core_running || break + runtime_run_health_check && break + _i=$((_i + 1)) + done + ) /dev/null 2>&1 & + return 1 +} + +_require_runtime_network_stack() { + local missing="" + + for _cmd in fw4 nft ip; do + command_exists "$_cmd" || missing="$missing $_cmd" + done + + if [ -n "$missing" ]; then + _log_runtime_error "Missing required fw4 runtime tools:$missing" + return 1 + fi + + return 0 +} + +_service_enabled() { + local svc="$1" + [ -x "/etc/init.d/${svc}" ] || return 1 + "/etc/init.d/${svc}" enabled >/dev/null 2>&1 +} + +# auto-detect core binary by priority: mihomo > clash-meta > built-in clash +_detect_core() { + for _bin in $(_core_candidates); do + [ -x "$_bin" ] && echo "$_bin" && return + done + echo "/etc/clashoo/clash" +} +CLASH="$(_detect_selected_core)" +CLASH_CONFIG="/etc/clashoo" +CRON_FILE="/etc/crontabs/root" +CONFIG_YAML="/etc/clashoo/config.yaml" + +CUSLIST="/tmp/dnsmasq.d/custom_list.conf" +CUSLITT="/tmp/dnsmasq.clashoo" +CUSLISTV="/var/dnsmasq.d/custom_list.conf" +CUSLITTV="/var/dnsmasq.clashoo" +REAL_LOG="/usr/share/clashoo/clashoo_real.txt" +RUNTIME_STATE_FILE="/tmp/clashoo/runtime_state" +DNS_STATE_DIR="/tmp/clashoo" +DNS_STATE_SERVER_FILE="${DNS_STATE_DIR}/dnsmasq_server.before" +DNS_STATE_NORESOLV_FILE="${DNS_STATE_DIR}/dnsmasq_noresolv.before" +DNS_STATE_CACHE_FILE="${DNS_STATE_DIR}/dnsmasq_cachesize.before" +DNS_STATE_RESOLV_FILE="${DNS_STATE_DIR}/dnsmasq_resolvfile.before" + +cleanup_legacy_startup_backups() { +#=========================================================================================================================== + # Historical backup artifacts can clutter LuCI "System -> Startup". + # Keep runtime clean by removing known obsolete backup filenames. + # The old `*bak*` glob also wiped other packages' backups (e.g. wireless.bak), + # so scope it to Clashoo's own script names (legacy `clash`, current `clashoo`). + # `._*` is macOS AppleDouble metadata, never a valid init script, so it stays. + rm -f /etc/init.d/._clash \ + /etc/init.d/._* \ + /etc/init.d/clash*bak* >/dev/null 2>&1 || true +#=========================================================================================================================== +} + +migrate_legacy_clash_dir() { + local legacy_dir target_dir legacy_path target_path legacy_name + legacy_dir="/etc/clash" + target_dir="/etc/clashoo" + + [ -d "$legacy_dir" ] || return 0 + mkdir -p "$target_dir" >/dev/null 2>&1 + + for legacy_name in config.yaml Country.mmdb geoip.metadb geosite.dat geoip.dat GeoLite2-ASN.mmdb; do + legacy_path="${legacy_dir}/${legacy_name}" + target_path="${target_dir}/${legacy_name}" + [ -e "$legacy_path" ] || continue + [ -e "$target_path" ] || mv -f "$legacy_path" "$target_path" >/dev/null 2>&1 + done + + for legacy_name in rules ruleset rule-set profiles; do + legacy_path="${legacy_dir}/${legacy_name}" + target_path="${target_dir}/${legacy_name}" + [ -e "$legacy_path" ] || continue + [ -e "$target_path" ] || mv -f "$legacy_path" "$target_path" >/dev/null 2>&1 + done + + rmdir "$legacy_dir" >/dev/null 2>&1 || true +} + +backup_dnsmasq_state() { +#=========================================================================================================================== + [ -f "$DNS_STATE_SERVER_FILE" ] && return 0 + mkdir -p "$DNS_STATE_DIR" >/dev/null 2>&1 + uci -q get dhcp.@dnsmasq[0].server >"$DNS_STATE_SERVER_FILE" 2>/dev/null || : >"$DNS_STATE_SERVER_FILE" + uci -q get dhcp.@dnsmasq[0].noresolv >"$DNS_STATE_NORESOLV_FILE" 2>/dev/null || : >"$DNS_STATE_NORESOLV_FILE" + uci -q get dhcp.@dnsmasq[0].cachesize >"$DNS_STATE_CACHE_FILE" 2>/dev/null || : >"$DNS_STATE_CACHE_FILE" + uci -q get dhcp.@dnsmasq[0].resolvfile >"$DNS_STATE_RESOLV_FILE" 2>/dev/null || : >"$DNS_STATE_RESOLV_FILE" +#=========================================================================================================================== +} + +add_clash_dns_server() { +#=========================================================================================================================== + local dns_port="$1" + printf '%s' "$dns_port" | grep -Eq '^[0-9]+$' || return 1 + uci del_list dhcp.@dnsmasq[0].server="127.0.0.1#${dns_port}" >/dev/null 2>&1 || true + uci add_list dhcp.@dnsmasq[0].server="127.0.0.1#${dns_port}" >/dev/null 2>&1 +#=========================================================================================================================== +} + +restore_dnsmasq_state() { +#=========================================================================================================================== + local token saved + [ -d "$DNS_STATE_DIR" ] || return 0 + + if [ -f "$DNS_STATE_SERVER_FILE" ]; then + saved="$(cat "$DNS_STATE_SERVER_FILE" 2>/dev/null)" + uci -q delete dhcp.@dnsmasq[0].server >/dev/null 2>&1 + for token in $saved; do + uci add_list dhcp.@dnsmasq[0].server="$token" >/dev/null 2>&1 + done + fi + + if [ -s "$DNS_STATE_NORESOLV_FILE" ]; then + uci set dhcp.@dnsmasq[0].noresolv="$(cat "$DNS_STATE_NORESOLV_FILE" 2>/dev/null)" >/dev/null 2>&1 + else + uci -q delete dhcp.@dnsmasq[0].noresolv >/dev/null 2>&1 + fi + + if [ -s "$DNS_STATE_CACHE_FILE" ]; then + uci set dhcp.@dnsmasq[0].cachesize="$(cat "$DNS_STATE_CACHE_FILE" 2>/dev/null)" >/dev/null 2>&1 + else + uci -q delete dhcp.@dnsmasq[0].cachesize >/dev/null 2>&1 + fi + + if [ -s "$DNS_STATE_RESOLV_FILE" ]; then + uci set dhcp.@dnsmasq[0].resolvfile="$(cat "$DNS_STATE_RESOLV_FILE" 2>/dev/null)" >/dev/null 2>&1 + else + uci -q delete dhcp.@dnsmasq[0].resolvfile >/dev/null 2>&1 + fi + + rm -f "$DNS_STATE_SERVER_FILE" "$DNS_STATE_NORESOLV_FILE" "$DNS_STATE_CACHE_FILE" "$DNS_STATE_RESOLV_FILE" >/dev/null 2>&1 + rmdir "$DNS_STATE_DIR" >/dev/null 2>&1 || true +#=========================================================================================================================== +} + +ensure_dns_when_core_stopped() { +#=========================================================================================================================== + local force="${1:-0}" servers token remote_servers + [ "$force" = "1" ] || ! _selected_core_running || return 0 + servers="$(uci -q get dhcp.@dnsmasq[0].server 2>/dev/null)" + remote_servers="" + for token in $servers; do + case "$token" in + 127.0.0.1#1053|127.0.0.1#5300|127.0.0.1#7874) ;; + *) remote_servers="${remote_servers} ${token}" ;; + esac + done + + if [ "$force" = "1" ] || [ -z "$remote_servers" ]; then + uci -q delete dhcp.@dnsmasq[0].server >/dev/null 2>&1 + for token in $remote_servers; do + uci add_list dhcp.@dnsmasq[0].server="$token" >/dev/null 2>&1 + done + fi + if [ -z "$remote_servers" ]; then + uci add_list dhcp.@dnsmasq[0].server='119.29.29.29' >/dev/null 2>&1 + uci add_list dhcp.@dnsmasq[0].server='223.5.5.5' >/dev/null 2>&1 + fi + if [ "$force" = "1" ] || [ -z "$remote_servers" ]; then + uci set dhcp.@dnsmasq[0].noresolv='0' >/dev/null 2>&1 + fi +#=========================================================================================================================== +} + +_dnsmasq_state_digest() { + local _nr _cs + _nr="$(uci -q get dhcp.@dnsmasq[0].noresolv 2>/dev/null)" + _cs="$(uci -q get dhcp.@dnsmasq[0].cachesize 2>/dev/null)" + case "$_nr" in ''|0|false) _nr=0 ;; *) _nr=1 ;; esac + [ -n "$_cs" ] || _cs=default + printf '%s|%s|%s|%s' \ + "$(uci -q get dhcp.@dnsmasq[0].server 2>/dev/null)" \ + "$_nr" "$_cs" \ + "$(uci -q get dhcp.@dnsmasq[0].resolvfile 2>/dev/null)" +} + +_reload_dnsmasq() { + /etc/init.d/dnsmasq reload >/dev/null 2>&1 && return 0 + /etc/init.d/dnsmasq restart >/dev/null 2>&1 +} + +revert_dns() { +#=========================================================================================================================== + local _before _after + _before="$(_dnsmasq_state_digest)" + restore_dnsmasq_state + ensure_dns_when_core_stopped "${1:-0}" + rm -rf "$CUSLIST" "$CUSLITT" "$CUSLISTV" "$CUSLITTV" 2>/dev/null + _after="$(_dnsmasq_state_digest)" + if [ "$_before" = "$_after" ]; then + uci -q revert dhcp + return 0 + fi + uci commit dhcp + _reload_dnsmasq >/dev/null 2>&1 +#=========================================================================================================================== +} + +add_cron(){ +#=========================================================================================================================== + # core.log 落在 tmpfs 上,按分钟卡一次大小;内核对日志没有上限, + # 一旦刷屏写满 /tmp,dnsmasq 与配置生成都会跟着挂。 + [ -z "$(grep -w "lib/core_log_guard.sh" "$CRON_FILE")" ] && echo "* * * * * sh /usr/share/clashoo/lib/core_log_guard.sh >/dev/null 2>&1" >> "$CRON_FILE" + + auto=$(uci get clashoo.config.auto_update 2>/dev/null) + clear=$(uci get clashoo.config.auto_clear_log 2>/dev/null) + if [ "${auto:-0}" -eq 1 ] || [ "${clear:-0}" -eq 1 ]; then + [ -z "$(grep -w "/usr/share/clashoo/update/maintenance_cron.sh" "$CRON_FILE")" ] && echo "23 * * * * sh /usr/share/clashoo/update/maintenance_cron.sh >/dev/null 2>&1" >> "$CRON_FILE" + fi + + sub_auto=$(uci get clashoo.config.auto_subscription_update 2>/dev/null) + if [ "${sub_auto:-0}" -eq 1 ]; then + [ -z "$(grep -w "/usr/share/clashoo/update/subscription_update_cron.sh" "$CRON_FILE")" ] && echo "7 * * * * sh /usr/share/clashoo/update/subscription_update_cron.sh >/dev/null 2>&1" >> "$CRON_FILE" + fi + + lgbm_auto=$(uci get clashoo.config.smart_lgbm_auto_update 2>/dev/null) + if [ "${lgbm_auto:-0}" -eq 1 ]; then + lgbm_interval=$(uci get clashoo.config.smart_lgbm_update_interval 2>/dev/null) + echo "$lgbm_interval" | grep -Eq '^[0-9]+$' || lgbm_interval=72 + [ "$lgbm_interval" -lt 1 ] && lgbm_interval=72 + lgbm_days=$(( lgbm_interval / 24 )) + [ "$lgbm_days" -lt 1 ] && lgbm_days=1 + [ -z "$(grep -w "update/lgbm_update.sh" "$CRON_FILE")" ] && echo "0 3 */$lgbm_days * * sh /usr/share/clashoo/update/lgbm_update.sh >/dev/null 2>&1" >> "$CRON_FILE" + fi + crontab "$CRON_FILE" +#=========================================================================================================================== +} + +del_cron(){ +#=========================================================================================================================== + sed -i '/update\/geoip.sh/d' "$CRON_FILE" + sed -i '/rpc_async.sh update_geoip/d' "$CRON_FILE" + sed -i '/\/usr\/share\/clash\/geoip.sh/d' "$CRON_FILE" + sed -i '/update\/update_all.sh/d' "$CRON_FILE" + sed -i '/\/usr\/share\/clash\/update_all.sh/d' "$CRON_FILE" + sed -i '/update\/maintenance_cron.sh/d' "$CRON_FILE" + sed -i '/lib\/core_log_guard.sh/d' "$CRON_FILE" + sed -i '/clashoo.txt/d' "$CRON_FILE" + sed -i '/update\/lgbm_update.sh/d' "$CRON_FILE" + sed -i '/\/usr\/share\/clash\/lgbm_update.sh/d' "$CRON_FILE" + sed -i '/update\/subscription_update_cron.sh/d' "$CRON_FILE" + /etc/init.d/cron restart +#=========================================================================================================================== +} + +refresh_cron() { + del_cron >/dev/null 2>&1 + [ "$(uci -q get clashoo.config.core_only 2>/dev/null)" != "1" ] && _selected_core_running && add_cron >/dev/null 2>&1 + return 0 +} + + +select_config(){ +#=========================================================================================================================== +CONFIG_YAML_PATH=$(uci get clashoo.config.use_config 2>/dev/null) +log_msg "Checking Config file" "正在检查配置文件" + +# Runtime dir may be missing (e.g. after a broken uninstall left the package +# installed but its /etc/clashoo dir removed). Recreate it so the cp below +# can't silently fail and leave the core without a config. +mkdir -p "$(dirname "$CONFIG_YAML")" 2>/dev/null + +if [ -f "$CONFIG_YAML_PATH" ] && [ -s "$CONFIG_YAML_PATH" ];then + + if ! cp "$CONFIG_YAML_PATH" "$CONFIG_YAML" 2>/dev/null || [ ! -s "$CONFIG_YAML" ];then + log_msg "Failed to stage config file" "配置写入失败:运行目录缺失或不可写" + sleep 5 + echo "Clashoo" >$REAL_LOG + return 1 + fi + +elif [ ! -f "$CONFIG_YAML_PATH" ] && [ ! -f "$CONFIG_YAML" ];then + + log_msg "No config found" "找不到配置文章" + sleep 5 + echo "Clashoo" >$REAL_LOG + return 1 + +elif [ ! -s "$CONFIG_YAML_PATH" ] && [ ! -s "$CONFIG_YAML" ];then + + log_msg "Your Config File is Empty" "你的config.yaml有问题还是为了空" + sleep 5 + echo "Clashoo" >$REAL_LOG + return 1 + +fi + +#=========================================================================================================================== +} + + + +_dns_roles_look_broken() { + local i=0 t a en broken=1 fb_total=0 fb_domestic=0 + while :; do + t="$(uci -q get clashoo.@dnsservers[$i].ser_type 2>/dev/null)" + [ -n "$t" ] || break + en="$(uci -q get clashoo.@dnsservers[$i].enabled 2>/dev/null)" + a="$(uci -q get clashoo.@dnsservers[$i].ser_address 2>/dev/null)" + i=$((i + 1)) + [ "$en" = "0" ] && continue + case "$t" in + nameserver) + printf '%s' "$a" | grep -qE '1\.1\.1\.1|1\.0\.0\.1|one\.one\.one\.one|cloudflare|8\.8\.8\.8|8\.8\.4\.4|dns\.google|9\.9\.9\.9' && broken=0 + ;; + fallback) + fb_total=$((fb_total + 1)) + printf '%s' "$a" | grep -qE '223\.5\.5\.5|223\.6\.6\.6|119\.29\.29\.29|119\.28\.28\.28|114\.114|180\.184|alidns|doh\.pub|dnspod' && fb_domestic=$((fb_domestic + 1)) + ;; + esac + done + [ "$fb_total" -gt 0 ] && [ "$fb_total" = "$fb_domestic" ] && broken=0 + return $broken +} + +_dns_drop_legacy_geolocation_policy() { + local i=0 matcher nameserver + while uci -q get "clashoo.@dns_policy[$i]" >/dev/null 2>&1; do + matcher="$(uci -q get "clashoo.@dns_policy[$i].matcher" 2>/dev/null)" + nameserver="$(uci -q get "clashoo.@dns_policy[$i].nameserver" 2>/dev/null)" + if [ "$matcher" = "geosite:geolocation-!cn" ] && [ "$nameserver" = "https://cloudflare-dns.com/dns-query" ]; then + uci -q delete "clashoo.@dns_policy[$i]" + continue + fi + i=$((i + 1)) + done +} + +_migrate_dns_roles() { + local ver + ver="$(uci -q get clashoo.config.dns_migrated 2>/dev/null)" + [ "$ver" = "3" ] && return 0 + if [ "$ver" = "2" ]; then + _dns_drop_legacy_geolocation_policy + uci -q set clashoo.config.dns_migrated='3' + uci -q commit clashoo + return 0 + fi + if [ "$ver" = "1" ] && ! _dns_roles_look_broken; then + _dns_drop_legacy_geolocation_policy + uci -q set clashoo.config.dns_migrated='3' + uci -q commit clashoo + return 0 + fi + while uci -q delete clashoo.@dnsservers[0] >/dev/null 2>&1; do :; done + while uci -q delete clashoo.@dns_policy[0] >/dev/null 2>&1; do :; done + _add_dnsserver() { + local sec + sec="$(uci -q add clashoo dnsservers)" || return 1 + uci -q set clashoo."$sec".enabled='1' + uci -q set clashoo."$sec".ser_type="$1" + uci -q set clashoo."$sec".ser_address="$2" + uci -q set clashoo."$sec".protocol="${3:-none}" + [ -n "$4" ] && uci -q set clashoo."$sec".ser_port="$4" + } + _add_dnsserver 'nameserver' 'https://dns.alidns.com/dns-query' + _add_dnsserver 'nameserver' 'https://doh.pub/dns-query' + _add_dnsserver 'proxy-server-nameserver' '1.1.1.1' 'tls://' '853' + _add_dnsserver 'fallback' 'https://cloudflare-dns.com/dns-query' + _add_dnsserver 'fallback' 'https://dns.google/dns-query' + _add_dnsserver 'direct-nameserver' '223.5.5.5' 'udp://' + local sec + sec="$(uci -q add clashoo dns_policy)" + uci -q set clashoo."$sec".enabled='1' + uci -q set clashoo."$sec".policy_type='nameserver-policy' + uci -q set clashoo."$sec".matcher='geosite:cn' + uci -q add_list clashoo."$sec".nameserver='https://dns.alidns.com/dns-query' + uci -q add_list clashoo."$sec".nameserver='https://doh.pub/dns-query' + uci -q set clashoo.config.dns_migrated='3' + uci -q commit clashoo + log_msg "Upstream DNS roles migrated" "上游 DNS 角色已恢复为经典防污染模型(国内主解析 + 境外 fallback)" +} + +_user_dns_fields() { + local field out="" force="${1:-}" + command -v yq >/dev/null 2>&1 || return 0 + [ -s "$CONFIG_YAML" ] || return 0 + for field in nameserver proxy-server-nameserver fallback direct-nameserver \ + default-nameserver nameserver-policy proxy-server-nameserver-policy respect-rules; do + case ",$force," in *,"$field",*) continue ;; esac + [ "$(yq -M e "(.dns // {}) | has(\"${field}\")" "$CONFIG_YAML" 2>/dev/null)" = "true" ] && + out="${out}${out:+,}${field}" + done + [ "$(yq -M e '.dns."prefer-h3" // false' "$CONFIG_YAML" 2>/dev/null)" = "true" ] && + out="${out}${out:+,}prefer-h3" + printf '%s' "$out" +} + +_dns_list_values() { + local field="$1" + command -v yq >/dev/null 2>&1 || return 0 + yq -M e -r "(.dns.\"${field}\" // [])[]" "$CONFIG_YAML" 2>/dev/null | sed '/^null$/d' +} + +_dns_value_is_domestic() { + case "$1" in + *223.5.5.5*|*119.29.29.29*|*114.114.114.114*|*180.184.*|*1.2.4.8*|*doh.pub*|*alidns*|*dns.pub*|*dnspod*) + return 0 ;; + esac + return 1 +} + +_dns_list_all_domestic() { + local field="$1" value seen=0 + for value in $(_dns_list_values "$field"); do + seen=1 + _dns_value_is_domestic "$value" || return 1 + done + [ "$seen" = "1" ] +} + +_dns_has_cn_rule_set() { + _dns_list_values "fake-ip-filter" | grep -qx 'rule-set:cn_domain' || return 1 + yq -M e -r '(.dns."nameserver-policy"."rule-set:cn_domain" // [])[]' "$CONFIG_YAML" 2>/dev/null | grep -q . +} + +_clashoo_dns_force_fields() { + command -v yq >/dev/null 2>&1 || return 0 + [ -s "$CONFIG_YAML" ] || return 0 + local managed legacy fields + fields="nameserver,proxy-server-nameserver,direct-nameserver,default-nameserver,nameserver-policy,proxy-server-nameserver-policy,respect-rules" + managed="$(yq -M e '(.dns."x-clashoo-managed-dns" // false) == true' "$CONFIG_YAML" 2>/dev/null)" + if [ "$managed" = "true" ]; then + printf '%s' "$fields" + return 0 + fi + legacy=0 + if _dns_has_cn_rule_set && + _dns_list_all_domestic "nameserver" && + _dns_list_all_domestic "proxy-server-nameserver"; then + legacy=1 + fi + [ "$legacy" = "1" ] && printf '%s' "$fields" + return 0 +} + +_dns_nameserver_inherit_mode() { + local has_nameserver marker prefer_h3 respect_false has_proxy force_respect + command -v yq >/dev/null 2>&1 || return 0 + [ -s "$CONFIG_YAML" ] || return 0 + + has_nameserver="$(yq -M e '(.dns.nameserver // null) | (tag != "!!null" and length > 0)' "$CONFIG_YAML" 2>/dev/null)" + [ "$has_nameserver" = "true" ] || return 0 + + marker=0 + if yq -M e -r '(.dns.nameserver // null) | .. | select(tag == "!!str")' "$CONFIG_YAML" 2>/dev/null | + awk ' + { + hash = index($0, "#") + if (!hash) next + fragment = substr($0, hash + 1) + split(fragment, parts, "&") + if (parts[1] !~ /=/) { found = 1; exit } + } + END { exit(found ? 0 : 1) }'; then + marker=1 + fi + prefer_h3="$(yq -M e '(.dns."prefer-h3" // false) == true' "$CONFIG_YAML" 2>/dev/null)" + respect_false="$(yq -M e '((.dns // {}) | has("respect-rules")) and ((.dns."respect-rules") == false or (.dns."respect-rules") == "false")' "$CONFIG_YAML" 2>/dev/null)" + has_proxy="$(yq -M e '(.dns // {}) | has("proxy-server-nameserver")' "$CONFIG_YAML" 2>/dev/null)" + force_respect=0 + case ",${CLASHOO_DNS_FORCE:-}," in + *,respect-rules,*) force_respect=1 ;; + esac + + # skip suppresses both the generated proxy role and automatic respect-rules. + if [ "$marker" = "1" ] || + { [ "$prefer_h3" = "true" ] && [ "$force_respect" != "1" ]; } || + { [ "$respect_false" = "true" ] && [ "$force_respect" != "1" ]; } || + [ "$(uci -q get clashoo.config.dns_respect_rules 2>/dev/null)" = "0" ]; then + printf '%s' skip + return 0 + fi + case ",${CLASHOO_DNS_FORCE:-}," in + *,proxy-server-nameserver,*) return 0 ;; + esac + [ "$has_proxy" = "true" ] && return 0 + printf '%s' inherit +} + +# issue #73: mihomo resolves proxy server domains through +# proxy-server-nameserver(-policy) and ignores nameserver-policy there. Airports +# ship their own resolver for node domains (e.g. "+.chylink.xyz" -> DoT) which +# answers with reachable relay IPs, so mirror the entries that match a node +# server domain before mixin drops them. +# +# Everything travels through a private 0700 work dir: large airports can produce +# hundreds of KB of policy entries, which neither argv nor env vars can carry +# (E2BIG), and predictable /tmp names are symlink bait. +_dns_node_policy_workdir="" + +_dns_node_policy_cleanup() { + [ -n "${_dns_node_policy_workdir:-}" ] || return 0 + rm -rf "$_dns_node_policy_workdir" 2>/dev/null + _dns_node_policy_workdir="" + return 0 +} + +# Same inputs yml_change feeds mixin: settle FORCE/PRESENT here so the skip +# decision cannot disagree with the config that mixin actually generates. +_dns_node_policy_mode() { + CLASHOO_DNS_FORCE="$(_clashoo_dns_force_fields)" + CLASHOO_DNS_PRESENT="$(_user_dns_fields "$CLASHOO_DNS_FORCE")" + export CLASHOO_DNS_FORCE CLASHOO_DNS_PRESENT + _dns_nameserver_inherit_mode +} + +_dns_node_policy_capture() { + _dns_node_policy_cleanup + command -v yq >/dev/null 2>&1 || return 0 + command -v ucode >/dev/null 2>&1 || return 0 + + local helper workdir + helper="${CLASHOO_NODE_DNS_UC:-/usr/share/clashoo/runtime/dns_node_policy.uc}" + [ -r "$helper" ] || return 0 + [ -s "$CONFIG_YAML" ] || return 0 + + # skip mode keeps the source DNS verbatim: mihomo already falls back to the + # normal DNS policy for node domains when no dedicated resolver is generated. + [ "$(_dns_node_policy_mode)" = "skip" ] && return 0 + + # sweep leftovers from an interrupted start; they are private to root anyway + find "${TMPDIR:-/tmp}" -maxdepth 1 -name 'clashoo-node-dns.*' -mmin +60 \ + -exec rm -rf {} + >/dev/null 2>&1 + + workdir="$(mktemp -d "${TMPDIR:-/tmp}/clashoo-node-dns.XXXXXX" 2>/dev/null)" || return 0 + chmod 700 "$workdir" 2>/dev/null + + if ! yq -M e -o=json -I0 '({"policies": (.dns."nameserver-policy" // {}), "servers": [(.proxies // [])[] | select(type == "!!map") | .server // ""]})' "$CONFIG_YAML" > "$workdir/source.json" 2>/dev/null || + [ ! -s "$workdir/source.json" ]; then + rm -rf "$workdir" + return 0 + fi + chmod 600 "$workdir/source.json" 2>/dev/null + + if ! CLASHOO_NODE_DNS_SOURCE_FILE="$workdir/source.json" ucode "$helper" > "$workdir/policy.json" 2>/dev/null || + [ ! -s "$workdir/policy.json" ]; then + rm -rf "$workdir" + return 0 + fi + chmod 600 "$workdir/policy.json" 2>/dev/null + rm -f "$workdir/source.json" + + if [ "$(yq -M e -r 'length' "$workdir/policy.json" 2>/dev/null)" = "0" ]; then + rm -rf "$workdir" + return 0 + fi + + _dns_node_policy_workdir="$workdir" + return 0 +} + +_dns_node_policy_apply() { + local policy_file rc + policy_file="${_dns_node_policy_workdir:+"$_dns_node_policy_workdir/policy.json"}" + if [ -z "$policy_file" ] || [ ! -s "$policy_file" ] || ! command -v yq >/dev/null 2>&1; then + _dns_node_policy_cleanup + return 0 + fi + + # existing keys keep their value; overlapping patterns are left to mihomo + CLASHOO_NODE_DNS_POLICY_FILE="$policy_file" yq -M e -i '(.dns // {}) as $dns | + (load(strenv(CLASHOO_NODE_DNS_POLICY_FILE)) // {}) as $derived | + .dns."proxy-server-nameserver-policy" = ($derived * ($dns."proxy-server-nameserver-policy" // {})) + ' "$CONFIG_YAML" 2>/dev/null + rc=$? + + # mihomo rejects a node policy without a proxy node resolver + if [ "$(yq -M e -r '((.dns."proxy-server-nameserver" // []) | length)' "$CONFIG_YAML" 2>/dev/null)" = "0" ]; then + yq -M e -i '.dns."proxy-server-nameserver" = (.dns."default-nameserver" // ["223.5.5.5"])' "$CONFIG_YAML" 2>/dev/null || true + fi + + _dns_node_policy_cleanup + [ "$rc" = "0" ] && log_msg "Inherited node domain DNS policy from subscription" "已从订阅继承节点域名 DNS 策略" + return 0 +} + +_dns_proxy_nameservers() { + local i=0 t en a p port s out="" prefix + while :; do + t="$(uci -q get clashoo.@dnsservers[$i].ser_type 2>/dev/null)" + [ -n "$t" ] || break + en="$(uci -q get clashoo.@dnsservers[$i].enabled 2>/dev/null)" + a="$(uci -q get clashoo.@dnsservers[$i].ser_address 2>/dev/null)" + p="$(uci -q get clashoo.@dnsservers[$i].protocol 2>/dev/null)" + port="$(uci -q get clashoo.@dnsservers[$i].ser_port 2>/dev/null)" + i=$((i + 1)) + [ "$t" = "proxy-server-nameserver" ] || continue + [ "$en" = "0" ] && continue + if printf '%s' "$a" | grep -Eq '^[A-Za-z][A-Za-z0-9+.-]*://'; then + s="$a" + else + case "$p" in + ''|none) prefix='' ;; + udp|udp://) prefix='udp://' ;; + tcp|tcp://) prefix='tcp://' ;; + dot|tls|tls://) prefix='tls://' ;; + doh|https|https://) prefix='https://' ;; + doq|quic|quic://) prefix='quic://' ;; + *) prefix="$p" ;; + esac + s="${prefix}${a}" + [ -n "$port" ] && s="${s}:${port}" + fi + [ -n "$s" ] && out="${out}${out:+ }${s}" + done + printf '%s' "$out" +} + +_adapt_dns_loopback_resolver() { + local filter listen host host_pattern old_port servers replacement tmp + [ "$(uci -q get clashoo.config.dns_loopback_compat 2>/dev/null)" = "1" ] || return 0 + command -v yq >/dev/null 2>&1 || return 0 + [ -s "$CONFIG_YAML" ] || return 0 + + filter="${CLASHOO_DNS_LOOPBACK_FILTER:-/usr/share/clashoo/runtime/dns_loopback_adapter.yq}" + [ -r "$filter" ] || return 0 + [ "$(yq -M e '(.dns."proxy-server-nameserver" | type)' "$CONFIG_YAML" 2>/dev/null)" = "!!seq" ] || return 0 + + listen="$(yq -M e -r '.dns.listen // ""' "$CONFIG_YAML" 2>/dev/null)" + old_port="${listen##*:}" + host="${listen%:*}" + printf '%s' "$old_port" | grep -Eq '^[0-9]+$' || return 0 + host="$(printf '%s' "$host" | tr 'A-Z' 'a-z')" + case "$host" in + ""|0.0.0.0|'[::]'|::) + host_pattern='(localhost|127(\.[0-9]{1,3}){3}|\[?::1\]?)' ;; + localhost) + host_pattern='localhost' ;; + 127.*) + printf '%s' "$host" | grep -Eq '^127(\.[0-9]{1,3}){3}$' || return 0 + host_pattern="$(printf '%s' "$host" | sed 's/\./\\./g')" ;; + '[::1]'|::1) + host_pattern='\[?::1\]?' ;; + *) + return 0 ;; + esac + + servers="$(yq -M e -r '.dns."proxy-server-nameserver"[]' "$CONFIG_YAML" 2>/dev/null)" + printf '%s\n' "$servers" | grep -Eiq "^([A-Za-z][A-Za-z0-9+.-]*://)?(${host_pattern}):${old_port}($|[/#?])" || return 0 + + replacement="$(_dns_proxy_nameservers)" + [ -n "$replacement" ] || replacement='tls://1.1.1.1:853' + tmp="$(mktemp "${CONFIG_YAML}.dns-loopback.XXXXXX" 2>/dev/null)" || return 0 + export CLASHOO_DNS_OLD_PORT="$old_port" + export CLASHOO_DNS_HOST_PATTERN="$host_pattern" + export CLASHOO_DNS_REPLACEMENT="$replacement" + if yq -M e --from-file "$filter" "$CONFIG_YAML" > "$tmp" 2>/dev/null && + mv -f "$tmp" "$CONFIG_YAML"; then + log_msg "Adapted stale loopback proxy DNS" "已适配失效的回环节点 DNS" + else + rm -f "$tmp" + log_msg "Loopback proxy DNS adaptation failed; preserved source settings" "回环节点 DNS 适配失败,已保留原配置" + fi + unset CLASHOO_DNS_OLD_PORT CLASHOO_DNS_HOST_PATTERN CLASHOO_DNS_REPLACEMENT + return 0 +} + +backfill_custom_sniffer_overrides() { + [ "$CLASHOO_KEEP_SNIFFER" = "1" ] || return 0 + [ "$(uci -q get clashoo.config.sniffer_streaming 2>/dev/null)" = "1" ] || return 0 + command -v yq >/dev/null 2>&1 || return 0 + [ -s "$CONFIG_YAML" ] || return 0 + [ -r /usr/share/clashoo/runtime/sniffer_overrides.yq ] || return 0 + yq -M e -i --from-file /usr/share/clashoo/runtime/sniffer_overrides.yq "$CONFIG_YAML" 2>/dev/null || true +} + +ensure_quic_fallback_guard() { + [ "$(uci -q get clashoo.config.sniffer_streaming 2>/dev/null)" = "1" ] || return 0 + [ "${EFFECTIVE_UDP_MODE:-off}" != "off" ] || return 0 + [ "$(uci -q get clashoo.config.block_quic 2>/dev/null)" != "1" ] || return 0 + command -v yq >/dev/null 2>&1 || return 0 + [ -s "$CONFIG_YAML" ] || return 0 + [ -r /usr/share/clashoo/runtime/quic_fallback_guard.yq ] || return 0 + yq -M e -i --from-file /usr/share/clashoo/runtime/quic_fallback_guard.yq "$CONFIG_YAML" 2>/dev/null || true +} + +yml_change() { + CLASHOO_DNS_FORCE="$(_clashoo_dns_force_fields)" + CLASHOO_DNS_PRESENT="$(_user_dns_fields "$CLASHOO_DNS_FORCE")" + CLASHOO_DNS_INHERIT_MODE="$(_dns_nameserver_inherit_mode)" + CLASHOO_DNS_INHERIT_NAMESERVER="" + if [ "$CLASHOO_DNS_INHERIT_MODE" = "inherit" ]; then + CLASHOO_DNS_INHERIT_NAMESERVER="$(yq -M e -o=json '(.dns.nameserver // [])' "$CONFIG_YAML" 2>/dev/null)" + fi + export CLASHOO_DNS_FORCE + export CLASHOO_DNS_PRESENT + export CLASHOO_DNS_INHERIT_MODE + export CLASHOO_DNS_INHERIT_NAMESERVER + CLASHOO_KEEP_FAKEIP_FILTER="" + CLASHOO_KEEP_SNIFFER="" + if command -v yq >/dev/null 2>&1 && [ -s "$CONFIG_YAML" ]; then + [ "$(yq -M e '(.dns // {}) | has("fake-ip-filter")' "$CONFIG_YAML" 2>/dev/null)" = "true" ] && + CLASHOO_KEEP_FAKEIP_FILTER=1 + [ "$(yq -M e 'has("sniffer")' "$CONFIG_YAML" 2>/dev/null)" = "true" ] && + CLASHOO_KEEP_SNIFFER=1 + fi + export CLASHOO_KEEP_FAKEIP_FILTER + export CLASHOO_KEEP_SNIFFER + ucode /usr/share/clashoo/runtime/mixin.uc 2>/dev/null | yq -M -p json -o yaml > /tmp/_clashoo_mixin.yaml 2>/dev/null && + yq -M eval-all '. as $item ireduce ({}; . * $item)' "$CONFIG_YAML" /tmp/_clashoo_mixin.yaml > "${CONFIG_YAML}.mixin" 2>/dev/null && + mv -f "${CONFIG_YAML}.mixin" "$CONFIG_YAML" 2>/dev/null && + rm -f /tmp/_clashoo_mixin.yaml 2>/dev/null + backfill_custom_sniffer_overrides + ensure_quic_fallback_guard +} + +_uci_fake_ip_filter_extras() { + local item list + list="$(uci -q get clashoo.config.fake_ip_filter 2>/dev/null)" + set -f + for item in $list; do + case "$item" in + '*.lan'|'localhost.ptlogin2.qq.com') + continue ;; + geosite:cn) + printf '%s\n' 'rule-set:cn_domain' ;; + *) + printf '%s\n' "$item" ;; + esac + done + set +f +} + +drop_legacy_cn_ip_injection() { + command -v yq >/dev/null 2>&1 || return 0 + [ -s "$CONFIG_YAML" ] || return 0 + + if ! yq -M e -i ' + . as $doc | + (select( + (.["rule-providers"].clashoo_cn_ip | length) == 4 and + .["rule-providers"].clashoo_cn_ip.type == "file" and + .["rule-providers"].clashoo_cn_ip.path == "./ruleset/cn-ip.txt" and + .["rule-providers"].clashoo_cn_ip.format == "text" and + .["rule-providers"].clashoo_cn_ip.behavior == "ipcidr" and + ([$doc | .. | select(tag == "!!str" and contains("clashoo_cn_ip")) | + select(. != "RULE-SET,clashoo_cn_ip,DIRECT")] | length) == 0 + )) |= ( + del(.["rule-providers"].clashoo_cn_ip) | + (select(has("rules")) | .rules) |= [.[] | select(. != "RULE-SET,clashoo_cn_ip,DIRECT")] + ) + ' "$CONFIG_YAML" 2>/dev/null; then + log_msg "Failed to remove legacy CN fallback injection" "清理旧版国内直连注入失败,请检查配置" + fi +} + +_effective_fake_ip_filter_mode() { + local mode + mode="$(uci -q get clashoo.config.fake_ip_filter_mode 2>/dev/null)" + if [ -z "$mode" ] && command -v yq >/dev/null 2>&1 && [ -s "$CONFIG_YAML" ]; then + mode="$(yq -M e '(.dns."fake-ip-filter-mode" // "") | tostring' "$CONFIG_YAML" 2>/dev/null)" + [ "$mode" = "null" ] && mode="" + fi + [ -n "$mode" ] || mode="blacklist" + printf '%s\n' "$mode" +} + +_shipped_fake_ip_filter_defaults() { + local f="/usr/share/clashoo/clashoo.default" + [ -s "$f" ] || return 0 + sed -n "s/^[[:space:]]*list[[:space:]]\{1,\}fake_ip_filter[[:space:]]\{1,\}'\{0,1\}\([^']*\)'\{0,1\}[[:space:]]*$/\1/p" "$f" +} + +_fake_ip_filter_as_rule() { + local f="$1" + case "$f" in + *,fake-ip|*,real-ip) printf '%s\n' "$f"; return ;; + esac + case "$f" in + *,*) printf '%s,real-ip\n' "$f" ;; + geosite:*) printf 'GEOSITE,%s,real-ip\n' "${f#geosite:}" ;; + rule-set:*) printf 'RULE-SET,%s,real-ip\n' "${f#rule-set:}" ;; + '+.'*) printf 'DOMAIN-SUFFIX,%s,real-ip\n' "${f#+.}" ;; + *'*'*) printf 'DOMAIN-WILDCARD,%s,real-ip\n' "$f" ;; + *) printf 'DOMAIN,%s,real-ip\n' "$f" ;; + esac +} + +_yq_apply_atomic() { + local expr="$1" cand + cand="${CONFIG_YAML}.cand.$$" + cp "$CONFIG_YAML" "$cand" 2>/dev/null || return 1 + if ! yq -M e -i "$expr" "$cand" 2>/dev/null; then + rm -f "$cand" 2>/dev/null + return 1 + fi + if ! yq -M e '.' "$cand" >/dev/null 2>&1; then + rm -f "$cand" 2>/dev/null + return 1 + fi + mv -f "$cand" "$CONFIG_YAML" 2>/dev/null || { rm -f "$cand" 2>/dev/null; return 1; } + return 0 +} + +normalize_fake_ip_filter() { + command -v yq >/dev/null 2>&1 || return 0 + [ -s "$CONFIG_YAML" ] || return 0 + + local mode dir work out item skip d + mode="$(_effective_fake_ip_filter_mode)" + + if [ "$mode" = "blacklist" ] || + [ "$(yq -M e '(.dns // {}) | has("fake-ip-filter")' "$CONFIG_YAML" 2>/dev/null)" != "true" ]; then + CLASHOO_FIF_MODE="$mode" _yq_apply_atomic '.dns."fake-ip-filter-mode" = strenv(CLASHOO_FIF_MODE)' || + log_msg "Failed to write fake-ip-filter-mode, subscription value kept" \ + "写入 fake-ip 过滤模式失败,已保留订阅自带的设置" + return 0 + fi + + dir="$(mktemp -d /tmp/.clashoo_fif.XXXXXX 2>/dev/null)" || return 0 + work="$dir/in" + out="$dir/out" + yq -M e '.dns."fake-ip-filter"[]' "$CONFIG_YAML" 2>/dev/null > "$work" || { rm -rf "$dir"; return 0; } + [ -s "$work" ] || { rm -rf "$dir"; return 0; } + + while IFS= read -r item; do + [ -n "$item" ] || continue + if [ "$mode" = "whitelist" ]; then + skip=0 + set -f + for d in $(_shipped_fake_ip_filter_defaults); do + [ "$item" = "$d" ] && skip=1 && break + done + set +f + [ "$skip" = "1" ] && continue + printf '%s\n' "$item" >> "$out" + else + _fake_ip_filter_as_rule "$item" >> "$out" + fi + done < "$work" + + if [ -s "$out" ]; then + CLASHOO_FIF_MODE="$mode" CLASHOO_FIF="$(cat "$out")" _yq_apply_atomic \ + '.dns."fake-ip-filter-mode" = strenv(CLASHOO_FIF_MODE) | .dns."fake-ip-filter" = (strenv(CLASHOO_FIF) | split("\n"))' || + log_msg "Failed to normalize fake-ip filter, subscription settings kept" \ + "规范化 fake-ip 过滤失败,已保留订阅自带的模式与列表" + else + CLASHOO_FIF_MODE="$mode" _yq_apply_atomic \ + '.dns."fake-ip-filter-mode" = strenv(CLASHOO_FIF_MODE) | del(.dns."fake-ip-filter")' || + log_msg "Failed to normalize fake-ip filter, subscription settings kept" \ + "规范化 fake-ip 过滤失败,已保留订阅自带的模式与列表" + log_msg "Fake-IP allowlist is empty after dropping shipped defaults, add your own entries" \ + "白名单模式下过滤列表已空(随包默认项在此模式下语义相反已跳过),请自行填写需要 Fake-IP 的域名" + fi + rm -rf "$dir" 2>/dev/null +} + +_ensure_cn_domain_rule_provider() { + command -v yq >/dev/null 2>&1 || return 0 + [ -s "$CONFIG_YAML" ] || return 0 + yq -M e -i ' + .["rule-providers"].cn_domain = { + "type": "file", + "path": "./ruleset/cn.mrs", + "format": "mrs", + "behavior": "domain" + } + ' "$CONFIG_YAML" 2>/dev/null || true +} + +append_luci_fake_ip_filter_extras() { + [ -n "${CLASHOO_KEEP_FAKEIP_FILTER:-}" ] || return 0 + command -v yq >/dev/null 2>&1 || return 0 + [ -s "$CONFIG_YAML" ] || return 0 + + local item needs_cn_file + needs_cn_file="/tmp/.clashoo_need_cn_rs.$$" + rm -f "$needs_cn_file" 2>/dev/null + _uci_fake_ip_filter_extras | while IFS= read -r item; do + [ -n "$item" ] || continue + [ "$item" = "rule-set:cn_domain" ] && : > "$needs_cn_file" + CLASHOO_FAKEIP_FILTER_ITEM="$item" yq -M e -i ' + .dns."fake-ip-filter" = (((.dns."fake-ip-filter" // []) + [strenv(CLASHOO_FAKEIP_FILTER_ITEM)]) | unique) + ' "$CONFIG_YAML" 2>/dev/null || true + done + [ -f "$needs_cn_file" ] && _ensure_cn_domain_rule_provider + rm -f "$needs_cn_file" 2>/dev/null +} + +ensure_dns_cn_policy_fallback() { + command -v yq >/dev/null 2>&1 || return 0 + [ -s "$CONFIG_YAML" ] || return 0 + [ "$(yq -M e '(.dns // {}) | has("nameserver-policy")' "$CONFIG_YAML" 2>/dev/null)" = "true" ] || return 0 + [ "$(yq -M e '((.dns."nameserver-policy" // {}) | type) == "!!map"' "$CONFIG_YAML" 2>/dev/null)" = "true" ] || return 0 + + if [ "$(yq -M e '(.dns."nameserver-policy" // {}) | has("rule-set:cn_domain")' "$CONFIG_YAML" 2>/dev/null)" = "true" ]; then + _ensure_cn_domain_rule_provider + return 0 + fi + + if [ "$(yq -M e '(.dns."nameserver-policy" // {}) | has("geosite:cn")' "$CONFIG_YAML" 2>/dev/null)" = "true" ]; then + yq -M e -i '.dns."nameserver-policy"."rule-set:cn_domain" = .dns."nameserver-policy"."geosite:cn"' "$CONFIG_YAML" 2>/dev/null || true + else + yq -M e -i '.dns."nameserver-policy"."rule-set:cn_domain" = ["https://223.5.5.5/dns-query", "https://doh.pub/dns-query"]' "$CONFIG_YAML" 2>/dev/null || true + fi + _ensure_cn_domain_rule_provider +} + +_remap_dns_system_nameserver() { + command -v yq >/dev/null 2>&1 || return 0 + [ -s "$CONFIG_YAML" ] || return 0 + yq -M e -i '(.dns | .. | select(. == "system")) |= "223.5.5.5"' "$CONFIG_YAML" 2>/dev/null || true +} + +ip_rules() { +#=========================================================================================================================== + CLASHOO_CONFIG_YAML="$CONFIG_YAML" sh /usr/share/clashoo/runtime/iprules.sh 2>/dev/null +#=========================================================================================================================== +} + + +smart_inject() { +#=========================================================================================================================== + local smart_auto smart_priority smart_asn smart_lgbm smart_collect smart_collect_size + local smart_collect_rate smart_lgbm_auto smart_lgbm_interval smart_lgbm_url + local prefer_asn_val lgbm_val collect_val collect_rate PRIORITY_TMP + + smart_auto=$(uci get clashoo.config.smart_auto_switch 2>/dev/null) + + # Remove any fields injected by a previous smart_inject run (idempotency) + sed -i '/^ uselightgbm:/d; /^ collectdata:/d; /^ prefer-asn:/d; /^ sample-rate:/d' "$CONFIG_YAML" 2>/dev/null + sed -i '/^ policy-priority:/d' "$CONFIG_YAML" 2>/dev/null + sed -i '/^lgbm-auto-update:/d; /^lgbm-url:/d; /^lgbm-update-interval:/d' "$CONFIG_YAML" 2>/dev/null + # revert previous smart injection: type:smart -> url-test + if command -v yq >/dev/null 2>&1; then + yq e -i '(.["proxy-groups"] // []) |= map(if .type == "smart" then .type = "url-test" else . end)' "$CONFIG_YAML" 2>/dev/null + fi + + [ "${smart_auto:-0}" != "1" ] && return 0 + # only inject smart fields when core=1; type:smart rejected by standard mihomo. + [ "$(uci -q get clashoo.config.core 2>/dev/null)" = "1" ] || return 0 + + smart_priority=$(uci get clashoo.config.smart_policy_priority 2>/dev/null) + smart_asn=$(uci get clashoo.config.smart_prefer_asn 2>/dev/null) + smart_lgbm=$(uci get clashoo.config.smart_uselightgbm 2>/dev/null) + smart_collect=$(uci get clashoo.config.smart_collectdata 2>/dev/null) + smart_collect_size=$(uci get clashoo.config.smart_collect_size 2>/dev/null) + smart_collect_rate=$(uci get clashoo.config.smart_collect_rate 2>/dev/null) + smart_lgbm_auto=$(uci get clashoo.config.smart_lgbm_auto_update 2>/dev/null) + smart_lgbm_interval=$(uci get clashoo.config.smart_lgbm_update_interval 2>/dev/null) + smart_lgbm_url=$(uci get clashoo.config.smart_lgbm_url 2>/dev/null) + + [ -z "$smart_collect_size" ] && smart_collect_size=100 + [ -z "$smart_collect_rate" ] && smart_collect_rate=1 + [ -z "$smart_lgbm_interval" ] && smart_lgbm_interval=72 + [ -z "$smart_lgbm_url" ] && smart_lgbm_url="https://github.com/vernesong/mihomo/releases/download/LightGBM-Model/Model.bin" + + [ "${smart_asn:-0}" = "1" ] && prefer_asn_val="true" || prefer_asn_val="false" + [ "${smart_lgbm:-0}" = "1" ] && lgbm_val="true" || lgbm_val="false" + [ "${smart_collect:-0}" = "1" ] && collect_val="true" || collect_val="false" + collect_rate="${smart_collect_rate:-1}" + + # Prefer structured YAML mutation (OpenClash-like behavior) when yq exists. + if command -v yq >/dev/null 2>&1; then + export SMART_PRIORITY="${smart_priority:-}" + export SMART_PREFER_ASN="$prefer_asn_val" + export SMART_USELGBM="$lgbm_val" + export SMART_COLLECT="$collect_val" + export SMART_COLLECT_RATE="${collect_rate:-1}" + export SMART_COLLECT_SIZE="${smart_collect_size:-100}" + export SMART_LGBM_AUTO="$([ "${smart_lgbm_auto:-0}" = "1" ] && echo "true" || echo "false")" + export SMART_LGBM_URL="${smart_lgbm_url:-}" + export SMART_LGBM_INTERVAL="${smart_lgbm_interval:-72}" + + if yq e ' + .["proxy-groups"] = ((.["proxy-groups"] // []) | map( + if (.type == "url-test" or .type == "load-balance" or .type == "smart") then + .type = "smart" | + .["prefer-asn"] = (env(SMART_PREFER_ASN) == "true") | + .uselightgbm = (env(SMART_USELGBM) == "true") | + .collectdata = (env(SMART_COLLECT) == "true") | + .["sample-rate"] = (env(SMART_COLLECT_RATE) | tonumber) | + (if env(SMART_PRIORITY) != "" then .["policy-priority"] = env(SMART_PRIORITY) else del(.["policy-priority"]) end) + else . + end + )) | + (if env(SMART_COLLECT) == "true" then + .profile = (.profile // {}) | + .profile["smart-collector-size"] = (env(SMART_COLLECT_SIZE) | tonumber) + else + . + end) | + (if env(SMART_USELGBM) == "true" then + .["lgbm-auto-update"] = (env(SMART_LGBM_AUTO) == "true") | + .["lgbm-url"] = env(SMART_LGBM_URL) | + .["lgbm-update-interval"] = (env(SMART_LGBM_INTERVAL) | tonumber) + else + del(.["lgbm-auto-update"], .["lgbm-url"], .["lgbm-update-interval"]) + end) + ' "$CONFIG_YAML" > /tmp/_smart_inject.yaml 2>/dev/null; then + mv /tmp/_smart_inject.yaml "$CONFIG_YAML" + unset SMART_PRIORITY SMART_PREFER_ASN SMART_USELGBM SMART_COLLECT SMART_COLLECT_RATE + unset SMART_COLLECT_SIZE SMART_LGBM_AUTO SMART_LGBM_URL SMART_LGBM_INTERVAL + return 0 + fi + + unset SMART_PRIORITY SMART_PREFER_ASN SMART_USELGBM SMART_COLLECT SMART_COLLECT_RATE + unset SMART_COLLECT_SIZE SMART_LGBM_AUTO SMART_LGBM_URL SMART_LGBM_INTERVAL + fi + + # Fallback path (no yq): line-based mutation with indentation-aware awk. + # Step 1: Replace any-indentation type: url-test/load-balance to type: smart + awk ' + /^[ \t]*type:[ \t]*(url-test|load-balance)[ \t]*$/ { + sub(/type:[ \t]*(url-test|load-balance)[ \t]*$/, "type: smart") + } + { print } + ' "$CONFIG_YAML" > /tmp/_smart_type.yaml && mv /tmp/_smart_type.yaml "$CONFIG_YAML" + + # Step 2: Build policy-priority value temp file (avoid quoting issues) + PRIORITY_TMP="/tmp/_smart_priority.txt" + printf '' > "$PRIORITY_TMP" + if [ -n "$smart_priority" ]; then + printf '%s\n' "$smart_priority" > "$PRIORITY_TMP" + fi + + # Step 3: Inject smart fields after each "type: smart" with preserved indentation + awk -v pfile="$PRIORITY_TMP" \ + -v prefer_asn="$prefer_asn_val" \ + -v lgbm="$lgbm_val" \ + -v collect="$collect_val" \ + -v rate="$collect_rate" ' + /^[ \t]*type:[ \t]*smart[ \t]*$/ { + match($0, /^[ \t]*/) + indent = substr($0, RSTART, RLENGTH) + print + while ((getline line < pfile) > 0) { + if (line != "") + print indent "policy-priority: \047" line "\047" + } + close(pfile) + print indent "prefer-asn: " prefer_asn + print indent "uselightgbm: " lgbm + print indent "collectdata: " collect + print indent "sample-rate: " rate + next + } + { print } + ' "$CONFIG_YAML" > /tmp/_smart_inject.yaml && mv /tmp/_smart_inject.yaml "$CONFIG_YAML" + rm -f "$PRIORITY_TMP" 2>/dev/null + + # Step 4: profile.smart-collector-size (preserve profile child indentation from source YAML) + if grep -q '^profile:[ ]*$' "$CONFIG_YAML"; then + profile_indent="$( + awk ' + /^profile:[ \t]*$/ { + while (getline > 0) { + if ($0 ~ /^[ \t]*$/) { + continue + } + if ($0 ~ /^[^ \t]/) { + break + } + match($0, /^[ \t]+/) + print substr($0, RSTART, RLENGTH) + exit + } + } + ' "$CONFIG_YAML" + )" + [ -n "$profile_indent" ] || profile_indent=" " + + if grep -q '^[ ]*smart-collector-size:[ ]*' "$CONFIG_YAML"; then + awk -v indent="$profile_indent" -v size="$smart_collect_size" ' + /^[ \t]*smart-collector-size:[ \t]*/ { + print indent "smart-collector-size: " size + next + } + { print } + ' "$CONFIG_YAML" > /tmp/_smart_profile.yaml && mv /tmp/_smart_profile.yaml "$CONFIG_YAML" + else + awk -v indent="$profile_indent" -v size="$smart_collect_size" ' + /^profile:[ \t]*$/ { + print + print indent "smart-collector-size: " size + next + } + { print } + ' "$CONFIG_YAML" > /tmp/_smart_profile.yaml && mv /tmp/_smart_profile.yaml "$CONFIG_YAML" + fi + else + printf '\nprofile:\n store-selected: true\n smart-collector-size: %s\n' "$smart_collect_size" >> "$CONFIG_YAML" + fi + + # Step 5: Top-level lgbm fields (only when lgbm enabled) + if [ "$lgbm_val" = "true" ]; then + lgbm_auto_val="false" + [ "${smart_lgbm_auto:-0}" = "1" ] && lgbm_auto_val="true" + printf 'lgbm-auto-update: %s\nlgbm-url: "%s"\nlgbm-update-interval: %s\n' \ + "$lgbm_auto_val" "$smart_lgbm_url" "$smart_lgbm_interval" >> "$CONFIG_YAML" + fi +#=========================================================================================================================== +} + + +_lan_acl_enabled() { + local section + for section in $(uci -q show clashoo 2>/dev/null | sed -n 's/^clashoo\.\([^.=]*\)=lan_acl$/\1/p'); do + [ "$(uci -q get "clashoo.${section}.enabled" 2>/dev/null)" != "0" ] && return 0 + done + [ "$(uci -q get clashoo.config.acl_migrated 2>/dev/null)" = "1" ] && return 1 + case "$(uci -q get clashoo.config.access_control 2>/dev/null)" in + 1|2) return 0 ;; + esac + return 1 +} + +_lan_dns_split_enabled() { + _lan_acl_enabled || return 1 + [ "$(uci -q get clashoo.config.enable_dns 2>/dev/null)" != "0" ] || return 1 + [ "$(uci -q get clashoo.config.ipv4_dns_hijack 2>/dev/null)" = "1" ] || + [ "$(uci -q get clashoo.config.ipv6_dns_hijack 2>/dev/null)" = "1" ] +} + +yml_dns_change(){ +#=========================================================================================================================== + dns_port=$(sed -n 's/^[[:space:]]*listen:.*:[[:space:]]*['"'"'"]*\([0-9]\{1,\}\)['"'"'"]*[[:space:]]*$/\1/p' "$CONFIG_YAML" 2>/dev/null | head -n1) + listen_port=$(uci get clashoo.config.listen_port 2>/dev/null) + [ -z "$listen_port" ] && listen_port=5300 + if [ -z "$dns_port" ]; then + dns_port="$listen_port" + if grep -q '^dns:' "$CONFIG_YAML" && ! grep -q "^ \{0,\}listen:" "$CONFIG_YAML"; then + sed -i "/^dns:/a\ listen: 0.0.0.0:${dns_port}" "$CONFIG_YAML" 2>/dev/null + fi + fi + if [ "${CLASHOO_RELOAD_CANDIDATE:-0}" = "1" ]; then + if [ "${dns_port:-0}" -eq 53 ]; then + sed -i "s@^\([[:space:]]*listen:[[:space:]]*\)0\.0\.0\.0:53@\10.0.0.0:${listen_port}@g" "$CONFIG_YAML" || return 1 + fi + return 0 + fi + dnsforwader=$(uci get clashoo.config.dnsforwader 2>/dev/null) + dnscache=$(uci get clashoo.config.dnscache 2>/dev/null) + if [ "$(uci -q get clashoo.config.enable_dns 2>/dev/null)" = "0" ]; then + revert_dns >/dev/null 2>&1 + return 0 + fi + if _lan_dns_split_enabled; then + revert_dns 1 >/dev/null 2>&1 + return 0 + fi + backup_dnsmasq_state + if [ "${dns_port:-0}" -eq 53 ]; then + sed -i "s@^\([[:space:]]*listen:[[:space:]]*\)0\.0\.0\.0:53@\10.0.0.0:${listen_port}@g" "$CONFIG_YAML" + dns_port="$listen_port" + fi + + if [ "${dnsforwader:-0}" -ne 0 ]; then + uci -q delete dhcp.@dnsmasq[0].server + if [ "${dns_port:-0}" -eq 53 ]; then + + log_msg "Setting dns forwarder" "设置dns转发器" + + add_clash_dns_server 5300 + uci delete dhcp.@dnsmasq[0].resolvfile + uci set dhcp.@dnsmasq[0].noresolv=1 + uci commit dhcp + if [ "${dnscache:-0}" -eq 0 ];then + log_msg "Disabling dns cache" "禁用dns缓存" + uci set dhcp.@dnsmasq[0].cachesize=0 + uci commit dhcp + fi + else + echo "Setting dns forwarder" >$REAL_LOG + add_clash_dns_server "$dns_port" + uci delete dhcp.@dnsmasq[0].resolvfile + uci set dhcp.@dnsmasq[0].noresolv=1 + uci commit dhcp + if [ "${dnscache:-0}" -eq 0 ];then + log_msg "Disabling dns cache" "禁用dns缓存" + + uci set dhcp.@dnsmasq[0].cachesize=0 + uci commit dhcp + fi + fi + elif [ "${dnsforwader:-0}" -ne 1 ]; then + if [ "${dnscache:-0}" -eq 0 ];then + log_msg "Disabling dns cache" "禁用dns缓存" + + uci set dhcp.@dnsmasq[0].cachesize=0 + uci commit dhcp + fi + fi +#=========================================================================================================================== +} + +singbox_dns_change() { +#=========================================================================================================================== + local dnsforwader dnscache dns_port + if _lan_dns_split_enabled; then + revert_dns 1 >/dev/null 2>&1 + return 0 + fi + dnsforwader="$(uci get clashoo.config.dnsforwader 2>/dev/null)" + if [ "${dnsforwader:-0}" -eq 0 ]; then + return 0 + fi + + dnscache="$(uci get clashoo.config.dnscache 2>/dev/null)" + dns_port="$(uci get clashoo.config.listen_port 2>/dev/null)" + [ -z "$dns_port" ] && dns_port=1053 + + backup_dnsmasq_state + _sb_dns_before="$(_dnsmasq_state_digest)" + uci -q delete dhcp.@dnsmasq[0].server + add_clash_dns_server "$dns_port" + uci -q delete dhcp.@dnsmasq[0].resolvfile + uci set dhcp.@dnsmasq[0].noresolv='1' + if [ "${dnscache:-0}" -eq 0 ]; then + uci set dhcp.@dnsmasq[0].cachesize='0' + fi + if [ "$(_dnsmasq_state_digest)" != "$_sb_dns_before" ]; then + uci commit dhcp >/dev/null 2>&1 + _reload_dnsmasq >/dev/null 2>&1 + else + uci -q revert dhcp >/dev/null 2>&1 + fi + unset _sb_dns_before +#=========================================================================================================================== +} + + +check(){ + + sed -i 's/^Proxy Group:/proxy-groups:/g' "$CONFIG_YAML" + sed -i 's/^proxy-provider:/proxy-providers:/g' "$CONFIG_YAML" + sed -i 's/^Proxy:/proxies:/g' "$CONFIG_YAML" + sed -i 's/^Rule:/rules:/g' "$CONFIG_YAML" + sed -i 's/^rule-provider:/rule-providers:/g' "$CONFIG_YAML" + +} + +rules(){ +#=========================================================================================================================== + /usr/share/clashoo/net/fw4.sh apply >/dev/null 2>&1 + return $? +#=========================================================================================================================== +} + + +restore() { + sh /usr/share/clashoo/runtime/restore.sh >/dev/null 2>&1 +} + +_config_needs_mmdb() { + grep -Eq '^[[:space:]]*-[[:space:]]*["'\'']?GEOIP,|^[[:space:]]*geoip:[[:space:]]|^[[:space:]]*geoip-code:[[:space:]]' "$CONFIG_YAML" +} + +_has_valid_local_mmdb() { + local db_file db_size + + for db_file in /etc/clashoo/geoip.metadb /etc/clashoo/Country.mmdb; do + [ -f "$db_file" ] || continue + db_size=$(wc -c <"$db_file" 2>/dev/null) + [ -n "$db_size" ] || continue + if [ "$db_file" = "/etc/clashoo/geoip.metadb" ] && [ "$db_size" -ge 1000000 ]; then + return 0 + fi + if [ "$db_file" = "/etc/clashoo/Country.mmdb" ] && [ "$db_size" -ge 2000000 ]; then + return 0 + fi + done + + return 1 +} + +_config_needs_geosite() { + # Matches: routing rules (- GEOSITE,...), geosite: child blocks, and + # list-item refs like `- geosite:cn` / `- 'geosite:cn'` used in + # fake-ip-filter (the default config ships one, easy to miss). + grep -Eq "^[[:space:]]*-[[:space:]]*[\"']?GEOSITE,|^[[:space:]]*geosite:[[:space:]]*|^[[:space:]]*[\"']geosite:|^[[:space:]]*-[[:space:]]*[\"']?geosite:" "$CONFIG_YAML" +} + +_has_valid_local_geosite() { + local site_file site_size + + for site_file in /etc/clashoo/GeoSite.dat /etc/clashoo/geosite.dat; do + [ -f "$site_file" ] || continue + site_size=$(wc -c <"$site_file" 2>/dev/null) + [ -n "$site_size" ] || continue + if [ "$site_size" -ge 2000000 ]; then + if [ "$site_file" = "/etc/clashoo/geosite.dat" ]; then + cp -f "$site_file" /etc/clashoo/GeoSite.dat >/dev/null 2>&1 || true + chmod 644 /etc/clashoo/GeoSite.dat >/dev/null 2>&1 || true + fi + return 0 + fi + done + + return 1 +} + +_strip_geox_url_block() { + awk ' + BEGIN { skip=0 } + /^geox-url:[[:space:]]*$/ { skip=1; next } + skip && /^[^[:space:]]/ { skip=0 } + !skip { print } + ' "$CONFIG_YAML" > /tmp/_clashoo_geox.yaml && mv /tmp/_clashoo_geox.yaml "$CONFIG_YAML" +} + +_strip_yaml_child_block() { + local regex="$1" + awk -v target="$regex" ' + function indent_of(line) { + match(line, /^[[:space:]]*/) + return RLENGTH + } + { + if (skip) { + if (length($0) == 0) { + next + } + if (indent_of($0) > base_indent) { + next + } + skip=0 + } + if ($0 ~ target) { + skip=1 + base_indent=indent_of($0) + next + } + print + } + ' "$CONFIG_YAML" > /tmp/_clashoo_geo_block.yaml && mv /tmp/_clashoo_geo_block.yaml "$CONFIG_YAML" +} + +_strip_yaml_rule_lines() { + local regex="$1" + awk -v target="$regex" ' + $0 ~ target { next } + { print } + ' "$CONFIG_YAML" > /tmp/_clashoo_geo_rules.yaml && mv /tmp/_clashoo_geo_rules.yaml "$CONFIG_YAML" +} + + +_start_geoip_update_async() { + if [ -f /var/run/geoip_update ]; then + return 0 + fi + touch /var/run/geoip_update >/dev/null 2>&1 + (exec 1000>&-; nohup /usr/share/clashoo/update/geoip.sh >/dev/null 2>&1 /dev/null 2>&1 + rm -f /etc/clashoo/geoip.metadb >/dev/null 2>&1 + log_msg "GeoIP database is invalid or incomplete, fallback to bootstrap mode" "GeoIP 数据库无效或下载不完整,已回退到引导模式" + fi + [ "$needs_geosite" -eq 1 ] && _has_valid_local_geosite && needs_geosite=0 + + if [ "$needs_mmdb" -eq 0 ] && [ "$needs_geosite" -eq 0 ]; then + return 0 + fi + + sed -i '/^geodata-mode:/d; /^geodata-loader:/d' "$CONFIG_YAML" 2>/dev/null + _strip_geox_url_block + + if [ "$needs_mmdb" -eq 1 ]; then + _strip_yaml_rule_lines "^[[:space:]]*-[[:space:]]*[\"']?GEOIP," + sed -i '/^[[:space:]]*geoip:[[:space:]]*/d' "$CONFIG_YAML" 2>/dev/null + sed -i '/^[[:space:]]*geoip-code:[[:space:]]*/d' "$CONFIG_YAML" 2>/dev/null + fi + + if [ "$needs_geosite" -eq 1 ]; then + rm -f /etc/clashoo/GeoSite.dat /etc/clashoo/geosite.dat >/dev/null 2>&1 + _strip_yaml_rule_lines "^[[:space:]]*-[[:space:]]*[\"']?GEOSITE," + _strip_yaml_child_block '^[[:space:]]*geosite:[[:space:]]*$' + _strip_yaml_child_block "^[[:space:]]*'geosite:[^']*'[[:space:]]*:[[:space:]]*$" + _strip_yaml_child_block '^[[:space:]]*"geosite:[^"]*"[[:space:]]*:[[:space:]]*$' + _strip_yaml_child_block '^[[:space:]]*geosite:[^[:space:]]+:[[:space:]]*$' + _strip_yaml_rule_lines '^[[:space:]]*geosite:[[:space:]]+[^[:space:]].*' + # list-item refs (e.g. fake-ip-filter: - geosite:cn / - 'geosite:cn') + sed -i "/^[[:space:]]*-[[:space:]]*[\"']\{0,1\}geosite:/d" "$CONFIG_YAML" 2>/dev/null + fi + + # Defer the geo download until the core is up: a fresh box has no working + # proxy yet, so fetching now would hit the (likely blocked) mirrors and fail. + # Record what was missing so the post-start watcher can fetch geo through the + # live proxy and only reload once geo is complete. See _finish_geo_bootstrap. + printf '%s %s\n' "$needs_mmdb" "$needs_geosite" > /var/run/clashoo_bootstrap_pending 2>/dev/null + log_msg "Geo data missing, started in bootstrap mode; will fetch geo via proxy then reload" "Geo 数据缺失,已切换为引导模式;待代理可用后自动补全并重载" +} + +# After the core is up in bootstrap mode, fetch the missing geo data through the +# now-working proxy and, once it is complete, reload the full (un-stripped) +# config so geo rules take effect. Loop-safe: only reloads when the geo that was +# actually missing is now valid, so the next start won't re-enter bootstrap. +_finish_geo_bootstrap() { + local need_mmdb need_geosite still + [ -f /var/run/clashoo_bootstrap_pending ] || return 0 + # core-only never strips, so a flag there would be stale — clear and skip. + if [ "$(uci -q get clashoo.config.core_only 2>/dev/null)" = "1" ]; then + rm -f /var/run/clashoo_bootstrap_pending >/dev/null 2>&1 + return 0 + fi + read need_mmdb need_geosite < /var/run/clashoo_bootstrap_pending 2>/dev/null + + log_msg "Bootstrap: fetching geo data via proxy" "引导模式:正在通过代理补全 Geo 数据" + if [ "$need_geosite" = "1" ]; then + /usr/share/clashoo/update/geoip.sh --require-geosite >/dev/null 2>&1 + else + /usr/share/clashoo/update/geoip.sh >/dev/null 2>&1 + fi + + still=0 + [ "$need_mmdb" = "1" ] && ! _has_valid_local_mmdb && still=1 + [ "$need_geosite" = "1" ] && ! _has_valid_local_geosite && still=1 + if [ "$still" = "1" ]; then + log_msg "Bootstrap: geo fetch incomplete, will retry on next update" "引导模式:Geo 补全未完成,将在下次更新时重试" + return 0 + fi + + rm -f /var/run/clashoo_bootstrap_pending >/dev/null 2>&1 + _clashoo_enabled || return 0 + log_msg "Bootstrap: geo ready, reloading full config" "引导模式:Geo 数据已就绪,正在重载完整配置" + /etc/init.d/clashoo restart >/dev/null 2>&1 +} + + + + + +_start_validate_core() { + local core bin + core="$(uci -q get clashoo.config.core 2>/dev/null)" + if is_singbox_selected; then + bin="$(selected_singbox_binary)" + if [ -z "$bin" ] || ! _ensure_singbox_binary; then + log_msg "sing-box core not found, please install the local sing-box binary first" "找不到本地 sing-box 内核,请先安装 sing-box 二进制" + echo "Clashoo" > "$REAL_LOG" + return 1 + fi + return 0 + fi + + if [ "${core:-0}" -eq 1 ] && [ ! -x "$CLASH" ]; then + log_msg "Smart core not found, please install the local Smart binary first" "找不到本地 Smart 内核,请先安装 Smart 二进制" + echo "Clashoo" > "$REAL_LOG" + return 1 + elif [ "${core:-0}" -eq 2 ] && [ ! -x "$CLASH" ]; then + log_msg "mihomo stable core not found, detected binary is unavailable: $CLASH" "找不到可用的 mihomo 稳定版内核: $CLASH" + sleep 1 + echo "Clashoo" > "$REAL_LOG" + return 1 + elif [ "${core:-0}" -eq 3 ] && [ ! -x "$CLASH" ]; then + log_msg "mihomo Alpha core not found, detected binary is unavailable: $CLASH" "找不到可用的 mihomo Alpha 内核,请在组件更新中安装: $CLASH" + sleep 1 + echo "Clashoo" > "$REAL_LOG" + return 1 + elif [ "${core:-0}" -ne 1 ] && [ "${core:-0}" -ne 2 ] && [ "${core:-0}" -ne 3 ] && [ ! -x "$CLASH" ]; then + log_msg "invalid core setting (${core}), detected binary is unavailable: $CLASH" "内核配置异常 (${core}),且找不到可用内核: $CLASH" + echo "Clashoo" > "$REAL_LOG" + return 1 + fi + return 0 +} + +_start_prepare_config() { + local _reload_candidate="${CLASHOO_RELOAD_CANDIDATE:-0}" + # Core-only: run the imported config verbatim. select_config already copied + # it to /etc/clashoo/config.yaml, so skip every mixin/DNS/rule rewrite below. + [ "$(uci -q get clashoo.config.core_only 2>/dev/null)" = "1" ] && return 0 + + check >/dev/null 2>&1 || return 1 + + [ "$_reload_candidate" = "1" ] || _migrate_dns_roles >/dev/null 2>&1 + + enable_dns=$(uci get clashoo.config.enable_dns 2>/dev/null) + + if [ -z "$(grep -E "^ {0,}listen:" "$CONFIG_YAML")" ] || [ -z "$(grep -E "^ {0,}enhanced-mode:" "$CONFIG_YAML")" ] || [ -z "$(grep -E "^ {0,}dns:" "$CONFIG_YAML")" ];then + + if [ "${enable_dns:-0}" -eq 0 ];then + [ "$_reload_candidate" = "1" ] && return 1 + uci set clashoo.config.enable_dns="1" && uci commit clashoo + log_msg "Enabling Custom Dns" "启用自定义DNS" + fi + + fi + + if { egrep -q '^ {0,}rule-providers:' "$CONFIG_YAML" || egrep -q '^ {0,}script:' "$CONFIG_YAML"; } && ! core_supports_advanced_features; then + [ "$_reload_candidate" = "1" ] && return 1 + if [ -n "$(_mihomo_stable_binary)" ]; then + uci set clashoo.config.core="2" && uci commit clashoo + elif [ -x /usr/share/clashoo/bin/mihomo-alpha ]; then + uci set clashoo.config.core="3" && uci commit clashoo + else + log_msg "Current config requires mihomo/clash-meta core" "当前配置需要 mihomo 或 clash-meta 内核" + backend_remove_rules >/dev/null 2>&1 + sleep 1 + echo "Clashoo" > "$REAL_LOG" + exit 0 + fi + core=$(uci get clashoo.config.core 2>/dev/null) + CLASH="$(_detect_selected_core)" + sleep 1 + fi + + if [ "$(uci get clashoo.config.p_mode 2>/dev/null)" = "script" ]; then + [ "$_reload_candidate" = "1" ] && return 1 + uci set clashoo.config.p_mode="rule" && uci commit clashoo + fi + + # flatten YAML merge anchors first (yq 4.53+ breaks "<<" keys on rewrite) + if command -v yq >/dev/null 2>&1 && grep -q '<<[[:space:]]*:' "$CONFIG_YAML" 2>/dev/null; then + yq e -i 'explode(.)' "$CONFIG_YAML" 2>/dev/null || true + fi + _adapt_dns_loopback_resolver >/dev/null 2>&1 + # stage bundled cn.mrs into the core dir; mihomo rejects rule-set paths outside /etc/clashoo + if [ "$_reload_candidate" != "1" ] && [ -f /usr/share/clashoo/ruleset/cn.mrs ]; then + if [ ! -f /etc/clashoo/ruleset/cn.mrs ] || [ /usr/share/clashoo/ruleset/cn.mrs -nt /etc/clashoo/ruleset/cn.mrs ]; then + mkdir -p /etc/clashoo/ruleset + cp -f /usr/share/clashoo/ruleset/cn.mrs /etc/clashoo/ruleset/cn.mrs 2>/dev/null + fi + fi + _dns_node_policy_capture + yml_change >/dev/null 2>&1 + _dns_node_policy_apply + append_luci_fake_ip_filter_extras >/dev/null 2>&1 + normalize_fake_ip_filter >/dev/null 2>&1 + drop_legacy_cn_ip_injection >/dev/null 2>&1 + dns_leak_protect=$(uci -q get clashoo.config.dns_leak_protect 2>/dev/null) + if command -v dns_mihomo_apply_leak_protect >/dev/null 2>&1; then + case "$(uci -q get clashoo.config.ipv6_proxy 2>/dev/null)" in + 1|true) _ipv6_proxied=1 ;; + *) _ipv6_proxied=0 ;; + esac + if [ "${dns_leak_protect:-0}" = "1" ] && [ "$_ipv6_proxied" = "0" ]; then + _ipv6_value="false" + else + case "$(uci -q get clashoo.config.enable_ipv6 2>/dev/null)" in + true|1) _ipv6_value="true" ;; + false|0) _ipv6_value="false" ;; + *) _ipv6_value="" ;; + esac + fi + unset _ipv6_proxied + dns_mihomo_apply_leak_protect "$CONFIG_YAML" "${dns_leak_protect:-0}" "$_ipv6_value" >/dev/null 2>&1 + unset _ipv6_value + fi + smart_inject >/dev/null 2>&1 + + if ! _has_valid_local_mmdb; then + log_msg "GeoIP database not found, continue without local GeoIP DB" "未找到本地 GeoIP 数据库,继续启动(可在系统页更新 GeoIP)" + fi + + [ "$_reload_candidate" = "1" ] || _prepare_geo_bootstrap_mode + _remap_dns_system_nameserver >/dev/null 2>&1 + ensure_dns_cn_policy_fallback >/dev/null 2>&1 + + + if [ ! -f "$CONFIG_YAML" ];then + [ "$_reload_candidate" = "1" ] && return 1 + log_msg "Cannot find config.yaml" "找不到config.yaml" + backend_remove_rules >/dev/null 2>&1 + sleep 1 + echo "Clashoo" > "$REAL_LOG" + exit 0 + fi + + yml_dns_change >/dev/null 2>&1 || return 1 + + # Sync processed config (with DNS listen injected) to mihomo working directory + [ "$_reload_candidate" = "1" ] || cp -f "$CONFIG_YAML" "${CLASH_CONFIG}/config.yaml" 2>/dev/null + + ip_rules >/dev/null 2>&1 +} + +_core_only_ui_args() { + [ "$(uci -q get clashoo.config.core_only 2>/dev/null)" = "1" ] || return 0 + [ -f /etc/clashoo/dashboard/index.html ] || return 0 + grep -Eq '^[[:space:]]*external-ui:' "$CONFIG_YAML" 2>/dev/null && return 0 + printf ' -ext-ui %s' "$(shell_quote /etc/clashoo/dashboard)" +} + +_mihomo_proxy_dns_valid() { + local config="$1" proxy_count policy_count respect_rules + command -v yq >/dev/null 2>&1 || return 0 + [ -s "$config" ] || return 0 + proxy_count="$(yq -M e -r '(.dns."proxy-server-nameserver" // []) | length' "$config" 2>/dev/null)" + [ "$proxy_count" = "0" ] || return 0 + policy_count="$(yq -M e -r '(.dns."proxy-server-nameserver-policy" // {}) | length' "$config" 2>/dev/null)" + respect_rules="$(yq -M e -r '.dns."respect-rules" // false' "$config" 2>/dev/null)" + [ "$policy_count" = "0" ] && [ "$respect_rules" != "true" ] +} + +_start_launch_core() { + local _cmd _core_only + disable_quic_gso=$(uci get clashoo.config.disable_quic_gso 2>/dev/null) + _core_only=$(uci -q get clashoo.config.core_only 2>/dev/null) + mkdir -p "$(dirname "$CORE_LOG_PATH")" >/dev/null 2>&1 + touch "$CORE_LOG_PATH" >/dev/null 2>&1 + # core.log 在 tmpfs 上,先按大小裁剪,避免上次跑飞的大日志直接卡死启动 + sh /usr/share/clashoo/lib/core_log_guard.sh "$CORE_LOG_PATH" >/dev/null 2>&1 + + # Core-only runs the imported config verbatim, which may reference absolute + # paths (e.g. OpenClash's external-ui /usr/share/openclash/ui). mihomo's + # SAFE_PATHS guard rejects paths outside the working dir, so relax it here — + # in core-only the user is explicitly trusting their own config. + [ "$_core_only" = "1" ] && export SAFE_PATHS="/" + + if ! _mihomo_proxy_dns_valid "${CLASH_CONFIG}/config.yaml"; then + log_msg "Proxy node DNS is required by node DNS policy or respect-rules; set a Proxy Node Domain Resolver in LuCI" "节点域名 DNS 策略或 DNS 遵循规则需要代理节点域名解析,请在 LuCI 中配置" + runtime_state_record_health "fail" "preflight:proxy_dns_missing" + echo "Clashoo" > "$REAL_LOG" + return 1 + fi + + # preflight dry-run with watchdog: bail on bad config without touching + # firewall/dnsmasq, and never let a geo auto-download (mihomo blocks ~90s on + # a missing GeoIP/GeoSite when GitHub is unreachable) freeze startup/UI. + _run_with_timeout 15 "$CLASH" -d "$CLASH_CONFIG" -t >> "$CORE_LOG_PATH" 2>&1 + _pf_rc=$? + if [ "$_pf_rc" -ne 0 ]; then + _pf_reason="$(_preflight_reason)" + if [ "$_pf_rc" -eq 124 ]; then + log_msg "Config preflight timed out (likely downloading geo data), retry shortly. ${_pf_reason}" "配置预检超时(可能正在下载 Geo 数据),稍后自动重试。${_pf_reason}" + runtime_state_record_health "fail" "preflight:timeout" + _start_geoip_update_async + else + log_msg "Config dry-run failed; firewall/DNS untouched. ${_pf_reason}" "配置预检失败,未应用防火墙与 DNS 改动。${_pf_reason}" + runtime_state_record_health "fail" "preflight:config_dry_run_failed" + fi + echo "Clashoo" > "$REAL_LOG" + return 1 + fi + + procd_open_instance + _cmd="$(shell_quote "$CLASH") -d $(shell_quote "$CLASH_CONFIG")$(_core_only_ui_args) >> $(shell_quote "$CORE_LOG_PATH") 2>&1" + procd_set_param command /bin/sh -c "$_cmd" + [ "$disable_quic_gso" = "1" ] && procd_set_param env QUIC_GO_DISABLE_GSO=1 + [ "$_core_only" = "1" ] && procd_set_param env SAFE_PATHS=/ + # 默认 4095 个 fd 顶不住 BT/PCDN 类连接洪水,打爆后核心会陷入 + # accept 失败刷屏,与 sing-box 实例保持一致提到 1000000。 + procd_set_param limits core="unlimited" + procd_set_param limits nofile="1000000 1000000" + procd_set_param respawn 3600 5 5 + procd_close_instance + [ "$disable_quic_gso" = "1" ] && log_msg "Disable quic-go GSO support" "已启用:禁用 quic-go GSO 支持" + ln -s /usr/share/clashoo/yacd /www/luci-static/yacd 2>/dev/null + + # Core-only: just keep the core process; do not take over firewall/DNS/cron. + if [ "$(uci -q get clashoo.config.core_only 2>/dev/null)" != "1" ]; then + log_msg "Configuring firewall rules" "配置防火墙规则" + backend_apply_rules >/dev/null 2>&1 + + if [ "$(_dnsmasq_state_digest)" != "$_dnsmasq_digest_before" ]; then + log_msg "Reloading DNS resolver" "重启 DNS 解析" + _reload_dnsmasq >/dev/null 2>&1 + fi + + restore >/dev/null 2>&1 + add_cron + else + log_msg "Core-only mode: skipping firewall/DNS takeover" "仅内核模式:跳过防火墙与 DNS 接管" + fi + echo "Clashoo" > "$REAL_LOG" + # procd starts the core process only after start_service() returns, so we + # cannot block here waiting for it. Launch a background watcher instead. + # If the core never appears within the window, roll back the firewall + # rules and dnsmasq override we already applied — otherwise the box is + # left with a hijacked DNS pointing at a port nobody is listening on. + ( + exec 1000>&- + _bg_max="$(_compute_start_wait)" + bg_wait=0 + while [ "$bg_wait" -lt "$_bg_max" ]; do + sleep 1 + if _core_running; then + runtime_run_health_check_settle >/dev/null 2>&1 + # kick access check immediately so panel shows current state (daemon polls every 5-20s) + /usr/share/clashoo/net/access_check_cache.sh >/dev/null 2>&1 & + # bootstrap mode: now that the proxy is up, fetch geo + reload full config + _finish_geo_bootstrap + exit 0 + fi + bg_wait=$((bg_wait + 1)) + done + _rollback_failed_start + runtime_state_record_health "fail" "start:core_not_running" + echo " $(date "+%Y-%m-%d %H:%M:%S") - clashoo core not detected within ${_bg_max}s, rolled back" >> /usr/share/clashoo/clashoo.txt + echo "Clashoo" > "$REAL_LOG" + ) & +} + +boot(){ + if [ "$(uci -q get clashoo.config.enable 2>/dev/null)" = "1" ]; then + runtime_state_reset + runtime_state_set "health_status" "starting" + runtime_state_set "health_detail" "boot_starting" + runtime_state_set "updated_at" "$(date +%s)" + # start_service does mihomo -t dry-run + firewall restart + dnsmasq reload/fallback + # (~20-30s on LXC). Doing this synchronously in boot() blocks rcS S99 stage + # and during that window sshd/uhttpd refuse connections (kex reset). Detach + # to background with a small settle delay so the rcS pipeline finishes and + # management plane stays responsive while clashoo brings the proxy up. + ( + exec 1000>&- /dev/null 2>&1 + sleep 5 + _clashoo_enabled || exit 0 + /etc/init.d/clashoo start + ) & + else + runtime_state_reset + runtime_state_set "health_status" "stopped" + runtime_state_set "health_detail" "boot_disabled" + runtime_state_set "updated_at" "$(date +%s)" + echo "Clashoo" > "$REAL_LOG" + fi + return 0 +} + +start_service(){ +#=========================================================================================================================== + lang=$(uci get luci.main.lang 2>/dev/null) + enable=$(uci get clashoo.config.enable 2>/dev/null) + mode=$(uci get clashoo.config.mode 2>/dev/null) + tun_mode=$(uci get clashoo.config.tun_mode 2>/dev/null) + core_only=$(uci -q get clashoo.config.core_only 2>/dev/null) + + sync_legacy_core_field + _migrate_legacy_mihomo_stable + CLASH="$(_detect_selected_core)" + + cleanup_legacy_startup_backups + migrate_legacy_clash_dir + /usr/share/clashoo/migrate_lan_acl.sh >/dev/null 2>&1 || true + /usr/share/clashoo/migrate_fake_ip_filter.sh >/dev/null 2>&1 || true + runtime_state_reset + + if [ "${enable:-0}" -eq 1 ]; then + resolve_effective_tp_modes + runtime_state_record_preflight "$(selected_core_family)" + log_msg "Starting Clashoo" "启动 Clashoo" + if is_singbox_selected; then + if _core_running; then stop_service >/dev/null 2>&1; fi + resolve_effective_tp_modes + runtime_state_record_preflight "singbox" + if ! _require_runtime_network_stack; then + runtime_state_record_health "fail" "preflight:missing_fw4_stack" + echo "Clashoo" > "$REAL_LOG" + return 1 + fi + if ! _start_validate_core; then + runtime_state_record_health "fail" "preflight:core_validation_failed" + return 1 + fi + if ! start_singbox_service; then + # preflight failure already recorded; avoid blanket overwrite + case "$(grep '^health_detail=' "$RUNTIME_STATE_FILE" 2>/dev/null | cut -d= -f2-)" in + preflight:*) ;; + *) runtime_state_record_health "fail" "start:singbox_service_failed" ;; + esac + echo "Clashoo" > "$REAL_LOG" + return 1 + fi + if [ "$core_only" != "1" ]; then + log_msg "Configuring firewall rules" "配置防火墙规则" + backend_apply_rules >/dev/null 2>&1 + singbox_dns_change >/dev/null 2>&1 + add_cron >/dev/null 2>&1 + else + log_msg "Core-only mode: skipping firewall/DNS takeover" "仅内核模式:跳过防火墙与 DNS 接管" + fi + # procd does not launch the named instance until start_service returns + # and rc.common submits the completed service definition. Watch it in + # the background, just like the mihomo path, then roll back only if the + # registered private instance never becomes healthy. + ( + exec 1000>&- + _bg_max="$(_compute_start_wait)" + _bg_wait=0 + while [ "$_bg_wait" -lt "$_bg_max" ]; do + sleep 1 + if _singbox_running; then + runtime_run_health_check_settle >/dev/null 2>&1 + exit 0 + fi + _bg_wait=$((_bg_wait + 1)) + done + _rollback_failed_start + runtime_state_record_health "fail" "start:singbox_not_running" + echo " $(date "+%Y-%m-%d %H:%M:%S") - clashoo sing-box not detected within ${_bg_max}s, rolled back" >> /usr/share/clashoo/clashoo.txt + echo "Clashoo" > "$REAL_LOG" + ) & + echo "Clashoo" > "$REAL_LOG" + return 0 + fi + if _core_running; then stop_service >/dev/null 2>&1; fi + if ! select_config >/dev/null 2>&1; then + runtime_state_record_health "fail" "preflight:config_missing" + _log_runtime_error "Config file is missing or empty; please import a subscription/config first" + return 1 + fi + + if ! _require_runtime_network_stack; then + runtime_state_record_health "fail" "preflight:missing_fw4_stack" + echo "Clashoo" > "$REAL_LOG" + exit 0 + fi + + +if ! _start_validate_core; then + runtime_state_record_health "fail" "preflight:core_validation_failed" + return 1 +fi + +if [ -f "$CONFIG_YAML" ] && [ -s "$CONFIG_YAML" ] ; then + + _dnsmasq_digest_before="$(_dnsmasq_state_digest)" + _start_prepare_config + + _start_launch_core + +else + if [ "${lang}" = "en" ]; then + echo " $(date "+%Y-%m-%d %H:%M:%S") - problem with config.yaml, config.yaml is either empty or not found " >> /usr/share/clashoo/clashoo.txt + elif [ "${lang}" = "zh_cn" ]; then + echo " $(date "+%Y-%m-%d %H:%M:%S") - 你的config.yaml有问题还是为了空 0kb " >> /usr/share/clashoo/clashoo.txt + fi + runtime_state_record_health "fail" "start:config_yaml_invalid" + log_msg "Config file invalid or empty" "配置文件无效或为空" + echo "Clashoo" > "$REAL_LOG" +fi + +else + runtime_state_set "health_status" "stopped" + runtime_state_set "health_detail" "service_disabled" + runtime_state_set "updated_at" "$(date +%s)" + if [ "${lang}" = "en" ]; then + echo " $(date "+%Y-%m-%d %H:%M:%S") - Enable Client and Start Client Again" >> /usr/share/clashoo/clashoo.txt + elif [ "${lang}" = "zh_cn" ]; then + echo " $(date "+%Y-%m-%d %H:%M:%S") - 启用客户端并重新启动客户端" >> /usr/share/clashoo/clashoo.txt + fi + echo "Clashoo" > "$REAL_LOG" +fi +#=========================================================================================================================== +} + + +remove_mark(){ + /usr/share/clashoo/net/fw4.sh remove >/dev/null 2>&1 + return 0 +} + +stop_service(){ +#=========================================================================================================================== + # The updater restarts once after an in-place APK upgrade. Skip the + # package-manager pre-deinstall stop to avoid its ~120s script wait. + if [ -r /var/run/clashoo_package_upgrade ]; then + _upgrade_pid="$(cat /var/run/clashoo_package_upgrade 2>/dev/null)" + case "$_upgrade_pid" in + ''|*[!0-9]*) rm -f /var/run/clashoo_package_upgrade ;; + *) + [ -d "/proc/$_upgrade_pid" ] && return 0 + rm -f /var/run/clashoo_package_upgrade + ;; + esac + fi + + lang=$(uci get luci.main.lang 2>/dev/null) + tun_mode=$(uci get clashoo.config.tun_mode 2>/dev/null) + + # server.list is read-only; just clean up merged temp files in /tmp + rm -f /tmp/clashoo/fake_filter.list /tmp/clashoo/fake_filter.yaml 2>/dev/null + + log_msg "Stopping Clashoo" "正在停止 Clashoo" + for _ac_sig in TERM KILL; do + for _ac_pid in $(pgrep -f access_check_daemon 2>/dev/null); do + tr '\0' ' ' < "/proc/$_ac_pid/cmdline" 2>/dev/null | grep -q access_check_daemon.sh && kill -"$_ac_sig" "$_ac_pid" 2>/dev/null + done + done + rm -rf /tmp/clashoo/access_check_daemon.pid /tmp/clashoo/access_check_daemon.lock 2>/dev/null + rm -f /var/run/clashoo_bootstrap_pending 2>/dev/null + log_msg "Removing firewall rules" "清理防火墙规则" + backend_remove_rules >/dev/null 2>&1 + cleanup_stale_core_nft >/dev/null 2>&1 + revert_dns >/dev/null 2>&1 + + sh /usr/share/clashoo/runtime/backup.sh >/dev/null 2>&1 + + stop_singbox_service + _kill_core + + rm -rf /www/luci-static/yacd 2>/dev/null + + del_cron >/dev/null 2>&1 + + + echo "" >/usr/share/clashoo/clashoo.txt >/dev/null 2>&1 + + echo "0" > /usr/share/clashoo/logstatus_check >/dev/null 2>&1 + + if [ "${lang}" = "en" ]; then + echo " $(date "+%Y-%m-%d %H:%M:%S") - CLIENT IS DISABLED " >> /usr/share/clashoo/clashoo.txt + elif [ "${lang}" = "zh_cn" ]; then + echo " $(date "+%Y-%m-%d %H:%M:%S") - 客户端被禁用 " >> /usr/share/clashoo/clashoo.txt + fi + echo "Clashoo" > "$REAL_LOG" + + if _core_running; then + _kill_core + fi + for _cpid in $(pgrep -f /etc/clashoo 2>/dev/null); do + _ccl="$(tr '\0' ' ' < "/proc/$_cpid/cmdline" 2>/dev/null)" + printf '%s' "$_ccl" | grep -q -e '-d /etc/clashoo' -e '-f /etc/clashoo/config.yaml' && kill "$_cpid" 2>/dev/null + done + runtime_state_reset + runtime_state_set "health_status" "stopped" + runtime_state_set "health_detail" "service_stopped" + runtime_state_set "updated_at" "$(date +%s)" + + # kick access check on stop (proxy_listening fast-paths to down; UI reflects stop within 5s) + (exec 1000>&-; /usr/share/clashoo/net/access_check_cache.sh >/dev/null 2>&1) & + +#=========================================================================================================================== +} + +_mihomo_reload_api() { + local _path="$1" _response="$2" _dp _ds _lip + _dp="$(uci -q get clashoo.config.dash_port)"; [ -n "$_dp" ] || _dp=9090 + _ds="$(uci -q get clashoo.config.dash_pass)" + _lip="$(uci -q get network.lan.ipaddr 2>/dev/null | awk -F'/' '{print $1}')" + [ -n "$_lip" ] || _lip=127.0.0.1 + MIHOMO_RELOAD_CURL_RC=0 + MIHOMO_RELOAD_HTTP_CODE="$(curl -sS -m 5 -o "$_response" -w '%{http_code}' \ + -H "Authorization: Bearer ${_ds}" -H "Content-Type: application/json" \ + -X PUT -d '{"path":"'"$_path"'"}' \ + "http://${_lip}:${_dp}/configs?force=true" 2>/dev/null)" || MIHOMO_RELOAD_CURL_RC=$? + [ "$MIHOMO_RELOAD_CURL_RC" -eq 0 ] || return 1 + case "$MIHOMO_RELOAD_HTTP_CODE" in + 2??) return 0 ;; + *) return 1 ;; + esac +} + +_mihomo_reload_save_dns() { + MIHOMO_DHCP_MODE="$(stat -c %a /etc/config/dhcp)" || return 1 + cp /etc/config/dhcp "$1" +} + +_mihomo_reload_restore_dns() { + cp "$1" /etc/config/dhcp && chmod "$MIHOMO_DHCP_MODE" /etc/config/dhcp && + uci -q revert dhcp && _reload_dnsmasq >/dev/null 2>&1 +} + +_mihomo_reload_restore() { + local _previous="$1" _response="$2" _dns_backup="$3" _peripheral_changed="$4" _restore _failed=0 + if ! _mihomo_reload_api "$_previous" "$_response"; then + return 1 + fi + if [ "$_peripheral_changed" = "1" ]; then + if _restore="$(mktemp "${CLASH_CONFIG}/.reload-restore.XXXXXX")"; then + cp "$_previous" "$_restore" && mv -f "$_restore" "$CONFIG_YAML" || { rm -f "$_restore"; _failed=1; } + else + _failed=1 + fi + _mihomo_reload_restore_dns "$_dns_backup" || _failed=1 + backend_remove_rules >/dev/null 2>&1 || _failed=1 + backend_apply_rules >/dev/null 2>&1 || _failed=1 + fi + return "$_failed" +} + +reload_service() +{ + if is_singbox_selected; then + if _singbox_running; then + # Use rc.common's transaction wrapper so the complete Clashoo service + # definition is rebuilt before the named instance is registered again. + # This intentionally reapplies Clashoo's own firewall/DNS state; calling + # start_service directly would skip that wrapper and procd setup. + stop_singbox_service >/dev/null 2>&1 + start + fi + elif _core_running; then + local _candidate _previous _response _dns_backup _source _pf_rc _error _peripheral_changed + _error="" + resolve_effective_tp_modes + _candidate="$(mktemp "${CLASH_CONFIG}/.reload-candidate.XXXXXX")" || { _log_runtime_error "Mihomo reload failed: cannot stage candidate"; return 1; } + _previous="$(mktemp "${CLASH_CONFIG}/.reload-previous.XXXXXX")" || { rm -f "$_candidate"; _log_runtime_error "Mihomo reload failed: cannot back up current config"; return 1; } + _response="$(mktemp /tmp/clashoo-reload-response.XXXXXX)" || { rm -f "$_candidate" "$_previous"; _log_runtime_error "Mihomo reload failed: cannot stage API response"; return 1; } + _dns_backup="$(mktemp "${CLASH_CONFIG}/.reload-dhcp.XXXXXX")" || { rm -f "$_candidate" "$_previous" "$_response"; _log_runtime_error "Mihomo reload failed: cannot back up DNS state"; return 1; } + _source="$(uci -q get clashoo.config.use_config 2>/dev/null)" + if [ ! -s "$_source" ]; then + _error="selected config is missing or empty" + elif ! cp "$_source" "$_candidate" || ! cp "$CONFIG_YAML" "$_previous" || + ! _mihomo_reload_save_dns "$_dns_backup"; then + _error="cannot back up current config or DNS state" + elif ! ( CONFIG_YAML="$_candidate"; CLASHOO_RELOAD_CANDIDATE=1; _start_prepare_config ) >/dev/null 2>&1 || + [ ! -s "$_candidate" ]; then + _error="candidate generation failed; check DNS and core settings" + fi + rm -f "${_candidate}.mixin" 2>/dev/null + if [ -n "$_error" ]; then + _log_runtime_error "Mihomo reload failed: ${_error}; current config preserved" + rm -f "$_candidate" "$_previous" "$_response" "$_dns_backup" + return 1 + fi + if ! _mihomo_proxy_dns_valid "$_candidate"; then + _log_runtime_error "Mihomo reload failed: node DNS policy or respect-rules requires a Proxy Node Domain Resolver; current config preserved" + rm -f "$_candidate" "$_previous" "$_response" "$_dns_backup" + return 1 + fi + if [ "$(uci -q get clashoo.config.core_only 2>/dev/null)" = "1" ]; then + SAFE_PATHS=/ _run_with_timeout 15 "$CLASH" -d "$CLASH_CONFIG" -f "$_candidate" -t >> "$CORE_LOG_PATH" 2>&1 + else + _run_with_timeout 15 "$CLASH" -d "$CLASH_CONFIG" -f "$_candidate" -t >> "$CORE_LOG_PATH" 2>&1 + fi + _pf_rc=$? + if [ "$_pf_rc" -ne 0 ]; then + if [ "$_pf_rc" -eq 124 ]; then + _error="Mihomo reload failed: candidate preflight timed out" + else + _error="Mihomo reload failed: candidate config invalid (exit ${_pf_rc}; see core log)" + fi + _log_runtime_error "$_error; current config preserved" + rm -f "$_candidate" "$_previous" "$_response" "$_dns_backup" + return 1 + fi + if ! _mihomo_reload_api "$_candidate" "$_response"; then + _error="Mihomo reload failed (curl ${MIHOMO_RELOAD_CURL_RC}, HTTP ${MIHOMO_RELOAD_HTTP_CODE})" + if _mihomo_reload_restore "$_previous" "$_response" "$_dns_backup" 0; then + _log_runtime_error "${_error}; previous config restored" + rm -f "$_candidate" "$_previous" "$_response" "$_dns_backup" + else + _log_runtime_error "${_error}; reload outcome unknown; candidate and backup retained" + rm -f "$_response" "$_dns_backup" + fi + return 1 + fi + _peripheral_changed=0 + if ! mv -f "$_candidate" "$CONFIG_YAML"; then + _error="Mihomo reload failed: cannot commit candidate config" + elif [ "$(uci -q get clashoo.config.core_only 2>/dev/null)" != "1" ]; then + _peripheral_changed=1 + if ! backend_remove_rules >/dev/null 2>&1 || ! backend_apply_rules >/dev/null 2>&1 || + ! yml_dns_change >/dev/null 2>&1 || ! _reload_dnsmasq >/dev/null 2>&1; then + _error="Mihomo reload failed: firewall or DNS update failed" + fi + fi + if [ -n "$_error" ]; then + if _mihomo_reload_restore "$_previous" "$_response" "$_dns_backup" "$_peripheral_changed"; then + _log_runtime_error "${_error}; previous config and settings restored" + rm -f "$_candidate" "$_previous" "$_response" "$_dns_backup" + else + _log_runtime_error "${_error}; rollback incomplete; previous config retained for recovery" + rm -f "$_candidate" "$_response" + fi + return 1 + fi + rm -f "$_previous" "$_response" "$_dns_backup" "${CONFIG_YAML}.dns-leak-nameserver" + runtime_state_record_preflight "mihomo" + runtime_run_health_check_settle >/dev/null 2>&1 + fi +} + +status_service() { + if _selected_core_running; then + echo "running" + else + echo "inactive" + fi +} diff --git a/clashoo/files/usr/share/clashoo/detect_core_arch.sh b/clashoo/files/usr/share/clashoo/detect_core_arch.sh new file mode 100755 index 00000000..fbb3defd --- /dev/null +++ b/clashoo/files/usr/share/clashoo/detect_core_arch.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +[ -n "$(uci -q get clashoo.config.download_core 2>/dev/null)" ] && exit 0 + +raw="$(uname -m 2>/dev/null)" +[ -n "$raw" ] || raw="$(apk --print-arch 2>/dev/null)" +[ -n "$raw" ] || raw="$(opkg status libc 2>/dev/null | awk -F': ' '/^Architecture/{print $2; exit}')" +[ -n "$raw" ] || exit 0 + +case "$raw" in + x86_64|amd64) arch="amd64-compatible" ;; + aarch64*|arm64) arch="arm64" ;; + armv7*|arm_cortex-a[7-9]*|arm_cortex-a1[0-9]*) arch="armv7" ;; + armv6*|arm_cortex-a[56]*) arch="armv6" ;; + arm*) arch="armv5" ;; + i[3-6]86) arch="386" ;; + mips64el*) arch="mips64le" ;; + mips64*) arch="mips64" ;; + mipsel*) arch="mipsle" ;; + mips*) arch="mips" ;; + *) exit 0 ;; +esac + +uci -q set clashoo.config.download_core="$arch" +uci -q commit clashoo +exit 0 diff --git a/clashoo/files/usr/share/clashoo/diagnose_bypass.sh b/clashoo/files/usr/share/clashoo/diagnose_bypass.sh new file mode 100644 index 00000000..0e01a30d --- /dev/null +++ b/clashoo/files/usr/share/clashoo/diagnose_bypass.sh @@ -0,0 +1,181 @@ +#!/bin/sh + +umask 077 + +CONFIG_YAML="${CONFIG_YAML:-/etc/clashoo/config.yaml}" +NFT_V4="${NFT_V4:-/usr/share/clashoo/nftables/geoip_cn.nft}" +NFT_V6="${NFT_V6:-/usr/share/clashoo/nftables/geoip6_cn.nft}" +MAX_CONN=200 + +TMPDIR_D="$(mktemp -d /tmp/.clashoo_diag.XXXXXX 2>/dev/null)" || exit 1 +trap 'rm -rf "$TMPDIR_D" 2>/dev/null' EXIT INT TERM + +uci_get() { + uci -q get "clashoo.config.$1" 2>/dev/null +} + +section() { + printf '\n===== %s =====\n' "$1" +} + +yesno() { + [ -n "$1" ] && [ "$1" != "0" ] && [ "$1" != "false" ] && echo "on" || echo "off" +} + +bool_on() { + case "$1" in + 1|true|TRUE|yes|on) return 0 ;; + *) return 1 ;; + esac +} + +bypass4="$(uci_get bypass_china)" +bypass6="$(uci_get bypass_china_ipv6)" +[ -n "$bypass6" ] || bypass6="$bypass4" + +section "配置摘要" +printf '服务开关 : %s\n' "$(yesno "$(uci_get enable)")" +printf '运行模式 : %s\n' "$(uci_get enhanced_mode)" +printf 'TCP / UDP : %s / %s\n' "$(uci_get tcp_mode)" "$(uci_get udp_mode)" +printf '大陆绕过 v4 : %s\n' "$(yesno "$bypass4")" +printf '大陆绕过 v6 : %s%s\n' "$(yesno "$bypass6")" \ + "$([ -z "$(uci_get bypass_china_ipv6)" ] && echo '(未设置,跟随 v4)')" +printf '接管端口 : %s' "$(uci_get bypass_port_mode)" +[ "$(uci_get bypass_port_mode)" = "custom" ] && printf ' (%s)' "$(uci_get bypass_port_custom)" +printf '\n' +printf 'IPv6 代理 : %s\n' "$(yesno "$(uci_get ipv6_proxy)")" +printf '阻断 QUIC : %s\n' "$(yesno "$(uci_get block_quic)")" + +if [ "$(uci_get bypass_port_mode)" = "common" ]; then + printf '\n注意: 接管端口为 common,只代理 22/53/80/443/8080/8443,\n' + printf ' 游戏与 P2P 常用的高位端口不会走代理。\n' +fi + +section "fake-ip 过滤" +if [ -s "$CONFIG_YAML" ]; then + if grep -qE '^[[:space:]]*fake-ip-filter:' "$CONFIG_YAML" 2>/dev/null; then + filter_mode="$(uci_get fake_ip_filter_mode)" + [ -n "$filter_mode" ] || filter_mode="$(yq -M e '(.dns."fake-ip-filter-mode" // "blacklist") | tostring' "$CONFIG_YAML" 2>/dev/null)" + if [ "$filter_mode" = "whitelist" ]; then + echo "白名单模式:列表中的域名使用 fake-IP" + elif grep -qiE 'rule-set:cn_domain|RULE-SET,[[:space:]]*cn_domain,[[:space:]]*real-ip' "$CONFIG_YAML" 2>/dev/null; then + echo "含 rule-set:cn_domain (国内域名返回真实 IP)" + else + echo "缺少 rule-set:cn_domain" + echo " 国内域名会拿到 fake-ip 走代理,与硬编码国内 IP 分成两条路。" + fi + else + echo "配置中无 fake-ip-filter" + fi +else + echo "找不到 $CONFIG_YAML" +fi + +section "内核分流规则" +if [ -s "$CONFIG_YAML" ]; then + echo "-- rules 末尾 5 条 --" + grep -E '^[[:space:]]*-[[:space:]]*["'"'"']?(GEOIP|RULE-SET|MATCH|IP-CIDR|DOMAIN)' "$CONFIG_YAML" 2>/dev/null | tail -5 +fi + +section "IP 库" +for f in "$NFT_V4" "$NFT_V6"; do + if [ -s "$f" ]; then + printf '%-46s %6s 条 %s\n' "$f" \ + "$(grep -cE '^[[:space:]]*[0-9a-fA-F].*/' "$f" 2>/dev/null)" \ + "$(date -r "$f" '+%Y-%m-%d %H:%M' 2>/dev/null)" + else + printf '%-46s 缺失\n' "$f" + fi +done +for f in /etc/clashoo/Country.mmdb /etc/clashoo/geoip.metadb; do + [ -f "$f" ] && printf '%-46s %s\n' "$f" "$(ls -lh "$f" 2>/dev/null | awk '{print $5}')" +done + +set_count() { + nft list set inet fw4 "$1" 2>/dev/null | tr ',' '\n' | grep -cE '[0-9a-fA-F]+[.:].*/' +} + +section "防火墙绕过规则" +if nft list sets inet fw4 2>/dev/null | grep -q clashoo_china; then + for ch in dstnat mangle_prerouting; do + nft -a list chain inet fw4 "$ch" 2>/dev/null | + grep -E '@clashoo_china6? ' | sed "s|^[[:space:]]*| ${ch}: |" + done + echo + echo "已加载元素数(nft auto-merge 会合并相邻网段,少于文件行数属正常):" + bool_on "$bypass4" && printf ' clashoo_china : %s\n' "$(set_count clashoo_china)" + bool_on "$bypass6" && printf ' clashoo_china6 : %s\n' "$(set_count clashoo_china6)" +else + echo "当前 nftables 中没有 clashoo_china 集合(绕过未启用或服务未运行)" +fi + +section "绕过命中检查" +if ! bool_on "$bypass4" && ! bool_on "$bypass6"; then + echo "两个绕过均未启用,所有被接管的流量都交给内核判定,无需检查" +else + SECRET="$(uci_get dash_pass)" + PORT="$(uci_get dash_port)" + LAN_IP="$(uci -q get network.lan.ipaddr 2>/dev/null | awk -F/ '{print $1}')" + [ -n "$LAN_IP" ] || LAN_IP="127.0.0.1" + CONN_JSON="$TMPDIR_D/connections.json" + + if [ -z "$PORT" ]; then + echo "未配置控制端口,跳过" + elif ! curl -m 5 -s -H "Authorization: Bearer ${SECRET}" \ + "http://${LAN_IP}:${PORT}/connections" -o "$CONN_JSON" 2>/dev/null; then + echo "无法访问内核 API (http://${LAN_IP}:${PORT}),跳过" + elif [ ! -s "$CONN_JSON" ]; then + echo "内核 API 返回为空,跳过" + else + jsonfilter -i "$CONN_JSON" -e '@["connections"][*]["metadata"]["destinationIP"]' \ + 2>/dev/null > "${CONN_JSON}.ip" + jsonfilter -i "$CONN_JSON" -e '@["connections"][*]["chains"][0]' \ + 2>/dev/null > "${CONN_JSON}.ch" + + total="$(wc -l < "${CONN_JSON}.ip" 2>/dev/null | tr -d ' ')" + [ -n "$total" ] || total=0 + + if [ "$total" -eq 0 ]; then + echo "当前无活动连接" + elif [ "$total" != "$(wc -l < "${CONN_JSON}.ch" 2>/dev/null | tr -d ' ')" ]; then + echo "内核 API 字段数不一致,跳过" + else + awk 'NR==FNR { a[FNR] = $0; next } { print a[FNR] "\t" $0 }' \ + "${CONN_JSON}.ip" "${CONN_JSON}.ch" 2>/dev/null \ + | awk -F'\t' '$2 != "DIRECT" && $1 != "" && $1 !~ /^(198\.18\.|::|fc00:|fd)/ { print $1 "\t" $2 }' \ + | sort -u | head -n "$MAX_CONN" > "${CONN_JSON}.chk" + + hit=0 + : > "${CONN_JSON}.bad" + while IFS="$(printf '\t')" read -r dip chain; do + [ -n "$dip" ] || continue + case "$dip" in + *:*) bool_on "$bypass6" || continue + set_name=clashoo_china6 ;; + *) bool_on "$bypass4" || continue + set_name=clashoo_china ;; + esac + if nft get element inet fw4 "$set_name" "{ $dip }" >/dev/null 2>&1; then + hit=$((hit + 1)) + printf ' %-40s -> %s\n' "$dip" "$chain" >> "${CONN_JSON}.bad" + fi + done < "${CONN_JSON}.chk" + + printf '活动连接 %s 条,其中走代理的去重目的地 %s 个(上限 %s)\n' \ + "$total" "$(wc -l < "${CONN_JSON}.chk" 2>/dev/null | tr -d ' ')" "$MAX_CONN" + if [ "$hit" -gt 0 ]; then + printf '命中绕过白名单却仍走代理: %s 个\n\n' "$hit" + head -20 "${CONN_JSON}.bad" + echo + echo "这些目的地在防火墙白名单内,本应直连,却出现在代理链路上。" + echo "可能是连接建立于规则变更之前,或该地址族的绕过规则未生效。" + else + echo "未发现白名单地址走代理的连接" + fi + rm -f "${CONN_JSON}.chk" "${CONN_JSON}.bad" 2>/dev/null + fi + rm -f "${CONN_JSON}.ip" "${CONN_JSON}.ch" 2>/dev/null + fi +fi + +printf '\n' diff --git a/clashoo/files/usr/share/clashoo/lib/core_log_guard.sh b/clashoo/files/usr/share/clashoo/lib/core_log_guard.sh new file mode 100644 index 00000000..afd5450f --- /dev/null +++ b/clashoo/files/usr/share/clashoo/lib/core_log_guard.sh @@ -0,0 +1,28 @@ +#!/bin/sh +# clashoo core log size guard +# 用法: core_log_guard.sh [logfile] +# +# core.log 落在 /var/log(= /tmp 下的 tmpfs),没有大小上限时一次 accept +# 失败风暴就能把它写到几百 MB,直接把 /tmp 写满(dnsmasq、配置生成、 +# cron 全部失效)。这里在超过上限时就地裁剪,只保留尾部内容。 +# 内核进程持有该文件的 O_APPEND fd,所以必须原地重写,不能替换 inode。 + +set -u + +LOG="${1:-/var/log/clashoo/core.log}" +MAX_KB="${CLASHOO_CORE_LOG_MAX_KB:-16384}" +KEEP_KB="${CLASHOO_CORE_LOG_KEEP_KB:-1024}" + +[ -f "$LOG" ] || exit 0 +size="$(wc -c <"$LOG" 2>/dev/null)" +case "${size:-}" in ''|*[!0-9]*) exit 0 ;; esac +[ "$size" -gt $((MAX_KB * 1024)) ] || exit 0 + +tmp="${LOG}.trim.$$" +if tail -c $((KEEP_KB * 1024)) "$LOG" >"$tmp" 2>/dev/null && cat "$tmp" >"$LOG" 2>/dev/null; then + rm -f "$tmp" +else + rm -f "$tmp" + : >"$LOG" 2>/dev/null || true +fi +exit 0 diff --git a/clashoo/files/usr/share/clashoo/lib/migrate_singbox.uc b/clashoo/files/usr/share/clashoo/lib/migrate_singbox.uc new file mode 100644 index 00000000..a200b982 --- /dev/null +++ b/clashoo/files/usr/share/clashoo/lib/migrate_singbox.uc @@ -0,0 +1,385 @@ +#!/usr/bin/ucode + +'use strict'; + +import { readfile, writefile } from 'fs'; + +// Standalone sing-box config version-migration, used by the init.d core-only +// path to upgrade an imported config (momo/homeproxy/etc.) to the format the +// current sing-box core accepts, WITHOUT clashoo normalize/takeover. +// +// IMPORTANT: migrate_singbox() below is kept byte-for-byte in sync with the +// copy in luci-app-clashoo/.../luci.clashoo (the UI "migrate" button). If you +// change one, change the other. + +function migrate_singbox(cfg) { + // --- Fix DNS servers: address-based → type-based, remove detour --- + if (cfg.dns && cfg.dns.servers) { + cfg.dns.servers = map(cfg.dns.servers || [], function(srv) { + let addr = srv.address || ''; + let new_srv = {}; + let skip = { address: 1, detour: 1, address_resolver: 1, address_strategy: 1 }; + for (let k in srv) if (!skip[k]) new_srv[k] = srv[k]; + + if (addr === 'fakeip') { + new_srv.type = 'fakeip'; + } else if (addr === 'local' || addr === '') { + new_srv.type = new_srv.type || 'local'; + } else if (match(addr, /^rcode:\/\//) || addr === 'rcode') { + new_srv._rcode = true; // mark for removal, handled via dns rule action + } else if (match(addr, /^h3:\/\//)) { + let host = replace(replace(addr, /^h3:\/\//, ''), /\/.*$/, ''); + new_srv.type = 'h3'; new_srv.server = host; + } else if (match(addr, /^https:\/\//)) { + let host = replace(replace(addr, /^https:\/\//, ''), /\/.*$/, ''); + new_srv.type = 'https'; new_srv.server = host; + } else if (match(addr, /^tls:\/\//)) { + new_srv.type = 'tls'; new_srv.server = replace(addr, /^tls:\/\//, ''); + } else if (match(addr, /^tcp:\/\//)) { + new_srv.type = 'tcp'; new_srv.server = replace(addr, /^tcp:\/\//, ''); + } else if (addr && !new_srv.type) { + new_srv.type = 'udp'; new_srv.server = addr; // plain IP → UDP + } else if (addr) { + new_srv.address = addr; // unknown format, keep as-is + } + return new_srv; + }); + } + + // --- Remove rcode servers (unsupported in 1.12+ new format), convert referencing rules to reject --- + let rcode_tags = {}; + if (cfg.dns && cfg.dns.servers) { + let good = []; + for (let srv in cfg.dns.servers) { + if (srv._rcode) rcode_tags[srv.tag] = true; + else push(good, srv); + } + cfg.dns.servers = good; + } + if (cfg.dns && cfg.dns.rules && length(keys(rcode_tags)) > 0) { + cfg.dns.rules = map(cfg.dns.rules || [], function(rule) { + if (rule.server && rcode_tags[rule.server]) { + let r = {}; + for (let k in rule) if (k !== 'server' && k !== 'action') r[k] = rule[k]; + r.action = 'reject'; + return r; + } + return rule; + }); + } + + // --- Remove legacy dns.fakeip.enabled (1.12+ fakeip is configured via type:"fakeip" server) --- + if (cfg.dns && cfg.dns.fakeip) { + let fp = cfg.dns.fakeip; + let new_fp = {}; + for (let k in fp) if (k !== 'enabled') new_fp[k] = fp[k]; + if (length(keys(new_fp)) > 0) cfg.dns.fakeip = new_fp; + else delete cfg.dns['fakeip']; + } + + // --- Fix DNS rules: add action:"route" when server is set --- + if (cfg.dns && cfg.dns.rules) { + cfg.dns.rules = map(cfg.dns.rules || [], function(rule) { + if (rule.server && !rule.action) { + let r = {}; + for (let k in rule) r[k] = rule[k]; + r.action = 'route'; + return r; + } + return rule; + }); + } + + // --- Fix legacy special outbounds (block / dns types) --- + let special = {}; + if (cfg.outbounds) { + let kept = []; + for (let ob in cfg.outbounds) { + if (ob.type === 'block' || ob.type === 'dns') { + special[ob.tag] = ob.type; + } else { + push(kept, ob); + } + } + cfg.outbounds = kept; + } + + // Patch route rules that pointed to removed special outbounds + if (cfg.route && cfg.route.rules && length(keys(special)) > 0) { + cfg.route.rules = map(cfg.route.rules || [], function(rule) { + let sp = rule.outbound && special[rule.outbound]; + if (!sp) return rule; + let r = {}; + for (let k in rule) if (k !== 'outbound') r[k] = rule[k]; + r.action = (sp === 'dns') ? 'hijack-dns' : 'reject'; + return r; + }); + } + + // --- Migrate geoip/geosite database refs to rule_set (removed in sing-box 1.12) --- + let needed_rule_sets = {}; + + let migrate_geo_rule = function(rule) { + let has_geo = rule.geoip || rule.geosite; + if (!has_geo) return rule; + let r = {}; + let new_rs = []; + for (let k in rule) { + if (k === 'geoip') { + for (let n in rule[k]) { + let tag = 'geoip-' + n; + needed_rule_sets[tag] = 'https://cdn.jsdelivr.net/gh/SagerNet/sing-geoip@rule-set/geoip-' + n + '.srs'; + push(new_rs, tag); + } + } else if (k === 'geosite') { + for (let n in rule[k]) { + let tag = 'geosite-' + n; + needed_rule_sets[tag] = 'https://cdn.jsdelivr.net/gh/SagerNet/sing-geosite@rule-set/geosite-' + n + '.srs'; + push(new_rs, tag); + } + } else { + r[k] = rule[k]; + } + } + // Merge new rule_set tags with any existing ones + let existing_rs = type(r.rule_set) === 'array' ? r.rule_set : + (r.rule_set ? [r.rule_set] : []); + for (let t in new_rs) push(existing_rs, t); + r.rule_set = existing_rs; + return r; + }; + + if (cfg.route && cfg.route.rules) + cfg.route.rules = map(cfg.route.rules, migrate_geo_rule); + if (cfg.dns && cfg.dns.rules) + cfg.dns.rules = map(cfg.dns.rules, migrate_geo_rule); + + // download_detour must be DIRECT: at first start rule-sets aren't loaded, so + // routing it via a proxy deadlocks (DNS recursion -> sing-box FATAL). + let pick_dl_detour = function() { + if (cfg.outbounds) { + for (let ob in cfg.outbounds) { + if (ob && ob.type === 'direct' && ob.tag) return ob.tag; + } + } + return 'DIRECT'; + }; + + // Add rule_set download entries for each referenced geo tag + if (length(keys(needed_rule_sets)) > 0) { + if (!cfg.route) cfg.route = {}; + if (!cfg.route.rule_set) cfg.route.rule_set = []; + let existing_tags = {}; + for (let rs in cfg.route.rule_set) existing_tags[rs.tag] = true; + let dl_detour = pick_dl_detour(); + for (let tag in keys(needed_rule_sets)) { + if (!existing_tags[tag]) { + push(cfg.route.rule_set, { + tag: tag, + type: 'remote', + format: 'binary', + url: needed_rule_sets[tag], + download_detour: dl_detour + }); + } + } + } + + // --- Remove deprecated 'outbound' DNS rule items (fatal in sing-box 1.13) --- + // Replacement: set route.default_domain_resolver to the plain IP-based resolver + let dns_resolver_tag = ''; + if (cfg.dns && cfg.dns.servers) { + for (let srv in cfg.dns.servers) { + if ((srv.type === 'udp' || srv.type === 'local') && srv.server && + match(srv.server, /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/)) { + dns_resolver_tag = srv.tag; + break; + } + } + } + if (cfg.dns && cfg.dns.rules) { + let filtered = []; + for (let rule in cfg.dns.rules) { + if (!rule.outbound) push(filtered, rule); + } + cfg.dns.rules = filtered; + } + // Set route.default_domain_resolver (replaces outbound:any DNS routing) + if (dns_resolver_tag) { + if (!cfg.route) cfg.route = {}; + if (!cfg.route.default_domain_resolver) + cfg.route.default_domain_resolver = dns_resolver_tag; + } + + // --- Fix DNS servers with domain-name server field: add domain_resolver --- + // sing-box 1.12+ requires domain_resolver when server= is a hostname (not IP) + if (cfg.dns && cfg.dns.servers && dns_resolver_tag) { + let domain_types = { https: 1, h3: 1, tls: 1, tcp: 1 }; + cfg.dns.servers = map(cfg.dns.servers, function(srv) { + if (!domain_types[srv.type]) return srv; + if (!srv.server || match(srv.server, /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/)) return srv; + if (srv.domain_resolver) return srv; + let r = {}; + for (let k in srv) r[k] = srv[k]; + r.domain_resolver = dns_resolver_tag; + return r; + }); + } + + // --- Fix DNS rules referencing non-existent server tags → action: reject --- + if (cfg.dns && cfg.dns.servers && cfg.dns.rules) { + let valid_servers = {}; + for (let srv in cfg.dns.servers) valid_servers[srv.tag] = true; + cfg.dns.rules = map(cfg.dns.rules, function(rule) { + if (!rule.server || valid_servers[rule.server]) return rule; + let r = {}; + for (let k in rule) if (k !== 'server' && k !== 'action') r[k] = rule[k]; + r.action = 'reject'; + return r; + }); + } + + // --- Fix reject method: sing-box 1.14 rejects "dropped", only "default"/"drop" --- + if (cfg.dns && cfg.dns.rules) { + cfg.dns.rules = map(cfg.dns.rules, function(rule) { + if (rule.action !== 'reject' || !rule.method) return rule; + let m = rule.method; + if (m === 'dropped') m = 'drop'; + if (m !== 'default' && m !== 'drop') { + // unknown value: drop it, sing-box defaults to NXDOMAIN + let r = {}; + for (let k in rule) if (k !== 'method') r[k] = rule[k]; + return r; + } + if (m === rule.method) return rule; + let r = {}; + for (let k in rule) r[k] = rule[k]; + r.method = m; + return r; + }); + } + + // --- Migrate TUN inet4_address/inet6_address -> address array (1.12+), and + // strip legacy inbound fields removed in 1.13 (sniff / sniff_override_destination / + // sniff_timeout / domain_strategy). If any inbound had sniff on, prepend a + // { "action": "sniff" } route rule as the equivalent. --- + let had_sniff = false; + if (cfg.inbounds) { + cfg.inbounds = map(cfg.inbounds, function(ib) { + let r = {}; + let addrs = []; + for (let k in ib) { + if (k === 'inet4_address') { + if (type(ib[k]) === 'array') { for (let a in ib[k]) push(addrs, a); } + else push(addrs, ib[k]); + } else if (k === 'inet6_address') { + if (type(ib[k]) === 'array') { for (let a in ib[k]) push(addrs, a); } + else push(addrs, ib[k]); + } else if (k === 'sniff') { + if (ib[k]) had_sniff = true; + } else if (k === 'sniff_override_destination' || k === 'sniff_timeout' || + k === 'domain_strategy') { + // drop — moved to route.rules actions in 1.13 + } else { + r[k] = ib[k]; + } + } + if (length(addrs) > 0) r.address = addrs; + return r; + }); + } + if (had_sniff) { + if (!cfg.route) cfg.route = {}; + if (!cfg.route.rules) cfg.route.rules = []; + let has_sniff_rule = false; + for (let rl in cfg.route.rules) + if (rl.action === 'sniff') { has_sniff_rule = true; break; } + if (!has_sniff_rule) { + let new_rules = [ { action: 'sniff' } ]; + for (let rl in cfg.route.rules) push(new_rules, rl); + cfg.route.rules = new_rules; + } + } + + // --- sing-box 1.14 requires route.default_domain_resolver; add one if missing, + // preferring dns.final, else the first dns.servers entry with a tag. --- + if (cfg.route && !cfg.route.default_domain_resolver) { + let resolver_tag = ''; + if (cfg.dns && cfg.dns.final) resolver_tag = cfg.dns.final; + else if (cfg.dns && cfg.dns.servers) { + for (let srv in cfg.dns.servers) + if (srv.tag) { resolver_tag = srv.tag; break; } + } + if (resolver_tag) + cfg.route.default_domain_resolver = { server: resolver_tag }; + } + + // --- Remove dangling outbound refs from selectors/urltest, and drop airline + // pseudo-nodes. Two cases: (1) subconverter may reference a missing tag (e.g. + // REJECT); (2) "Traffic:.../Expire:.../quota" pseudo-nodes are real SS/Vmess + // outbounds (kept so the UI can read traffic/expiry) but don't forward — in a + // selector/urltest they win (0ms) and swallow all foreign traffic. --- + let is_pseudo_tag = function(t) { + if (!t) return false; + return match(t, /^Traffic[::]/) || + match(t, /^Expire[::]/) || + match(t, /剩余流量|剩余[::]/) || + match(t, /距离下次重置/) || + match(t, /到期(时间|日期)?[::]/) || + match(t, /官网[::]|网站[::]|套餐[::]?|客服[::]/) || + match(t, /QQ[群]?[::]/) || + match(t, /Telegram|TG群|官方群/) || + match(t, /续费|订阅地址|流量重置/); + }; + if (cfg.outbounds) { + let defined_tags = {}; + for (let ob in cfg.outbounds) defined_tags[ob.tag] = true; + cfg.outbounds = map(cfg.outbounds, function(ob) { + if ((ob.type !== 'selector' && ob.type !== 'urltest' && ob.type !== 'loadbalance') || + !ob.outbounds) return ob; + let valid = []; + for (let t in ob.outbounds) { + if (!defined_tags[t]) continue; + if (is_pseudo_tag(t)) continue; + push(valid, t); + } + if (length(valid) === length(ob.outbounds)) return ob; + let r = {}; + for (let k in ob) r[k] = ob[k]; + r.outbounds = valid; + return r; + }); + } + + // --- Force download_detour=DIRECT on every remote rule_set --- + // subconverter often emits download_detour="auto", but the proxy isn't ready at + // first start -> urltest deadlock ("fetch rule-set: deadline exceeded") -> FATAL. + // The srs URLs already use a CN-reachable mirror, so DIRECT is always correct. + if (cfg.route && cfg.route.rule_set) { + let dl_detour = pick_dl_detour(); + cfg.route.rule_set = map(cfg.route.rule_set, function(rs) { + if (!rs || rs.type !== 'remote') return rs; + let r = {}; + for (let k in rs) r[k] = rs[k]; + r.download_detour = dl_detour; + return r; + }); + } + + return cfg; +} + + +// ---- CLI entry: ucode migrate_singbox.uc ---- +let _path = ARGV[0] || ''; +if (!_path) { print("missing path\n"); exit(1); } +let _raw = readfile(_path); +if (!_raw) { print("read failed\n"); exit(1); } +let _cfg = json(_raw); +if (!_cfg) { print("json parse failed\n"); exit(1); } +let _before = sprintf('%J', _cfg); +_cfg = migrate_singbox(_cfg); +let _after = sprintf('%J', _cfg); +if (_before === _after) { print("nochange\n"); exit(0); } +if (writefile(_path, _after) === null) { print("write failed\n"); exit(1); } +print("migrated\n"); diff --git a/clashoo/files/usr/share/clashoo/lib/normalize_singbox_config.uc b/clashoo/files/usr/share/clashoo/lib/normalize_singbox_config.uc new file mode 100644 index 00000000..cd6a1d3c --- /dev/null +++ b/clashoo/files/usr/share/clashoo/lib/normalize_singbox_config.uc @@ -0,0 +1,939 @@ +#!/usr/bin/ucode + +'use strict'; + +import { readfile, writefile, access, popen } from 'fs'; + +let path = ARGV[0] || ''; +let redir_port = +(ARGV[1] || '7891'); +let tproxy_port = +(ARGV[2] || '7982'); +let mixed_port = +(ARGV[3] || '7890'); +let has_tun_device = (ARGV[4] || '1') == '1'; +if (has_tun_device && system('(ip tuntap add mode tun name cotuntest >/dev/null 2>&1 && ip link del cotuntest >/dev/null 2>&1)') != 0) + has_tun_device = false; + +// default 6666 must match fw4.sh CORE_ROUTING_MARK (0x1a0a) +let routing_mark = +(ARGV[5] || '6666'); +let dns_port = +(ARGV[6] || '1053'); +let dash_port = +(ARGV[7] || '9090'); +let dash_secret = ARGV[8] != null ? (ARGV[8] + '') : ''; + +if (!path) { + print("missing path\n"); + exit(1); +} + +let raw = readfile(path); +if (!raw) { + print("read failed\n"); + exit(1); +} + +let cfg = json(raw); +if (!cfg) { + print("json parse failed\n"); + exit(1); +} + +function s_len(s) { + return length(s || ''); +} + +function s_sub(s, start, count) { + if (count == null) + return substr(s || '', start); + return substr(s || '', start, count); +} + +function is_space(ch) { + return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n'; +} + +function trim_s(s) { + s = (s == null) ? '' : (s + ''); + while (s_len(s) > 0 && is_space(s_sub(s, 0, 1))) + s = s_sub(s, 1); + while (s_len(s) > 0 && is_space(s_sub(s, s_len(s) - 1, 1))) + s = s_sub(s, 0, s_len(s) - 1); + return s; +} + +let default_interface = ''; +let _default_if_pipe = popen("ip -4 route show default 2>/dev/null | awk '{print $5; exit}'"); +if (_default_if_pipe) { + default_interface = trim_s(_default_if_pipe.read('all')); + _default_if_pipe.close(); +} + +function starts_with(s, prefix) { + return s_sub(s || '', 0, s_len(prefix)) == prefix; +} + +function find_char(s, ch) { + for (let i = 0; i < s_len(s); i++) + if (s_sub(s, i, 1) == ch) + return i; + return -1; +} + +function find_last_char(s, ch) { + for (let i = s_len(s) - 1; i >= 0; i--) + if (s_sub(s, i, 1) == ch) + return i; + return -1; +} + +function split_uci_words(line) { + let out = [], cur = '', quote = '', esc = false; + for (let i = 0; i < s_len(line); i++) { + let ch = s_sub(line, i, 1); + if (esc) { + cur += ch; + esc = false; + continue; + } + if (ch == '\\') { + esc = true; + continue; + } + if (quote) { + if (ch == quote) + quote = ''; + else + cur += ch; + continue; + } + if (ch == '"' || ch == "'") { + quote = ch; + continue; + } + if (is_space(ch)) { + if (s_len(cur) > 0) { + push(out, cur); + cur = ''; + } + continue; + } + cur += ch; + } + if (s_len(cur) > 0) + push(out, cur); + return out; +} + +function load_clashoo_uci() { + let uci_path = ARGV[9] || '/etc/config/clashoo'; + let txt = readfile(uci_path) || ''; + let sections = [], cur = null; + for (let line in split(txt, '\n')) { + line = trim_s(line); + if (!s_len(line) || starts_with(line, '#')) + continue; + let words = split_uci_words(line); + if (!length(words)) + continue; + if (words[0] == 'config') { + cur = { type: words[1] || '', name: words[2] || '', options: {}, lists: {} }; + push(sections, cur); + continue; + } + if (!cur || length(words) < 3) + continue; + if (words[0] == 'option') + cur.options[words[1]] = words[2]; + else if (words[0] == 'list') { + if (cur.lists[words[1]] == null) + cur.lists[words[1]] = []; + push(cur.lists[words[1]], words[2]); + } + } + return sections; +} + +let clashoo_uci = load_clashoo_uci(); + +function uci_config_section() { + for (let s in clashoo_uci) + if (s.type == 'clashoo' && (s.name == 'config' || s.name == '')) + return s; + return {}; +} + +let uci_cfg = uci_config_section(); + +function uci_opt(key, def) { + if (uci_cfg.options && uci_cfg.options[key] != null) + return uci_cfg.options[key]; + return def; +} + +function uci_list(key) { + if (uci_cfg.lists && uci_cfg.lists[key] != null) + return uci_cfg.lists[key]; + if (uci_cfg.options && uci_cfg.options[key] != null) + return [ uci_cfg.options[key] ]; + return []; +} + +function uci_sections(type_name) { + let out = []; + for (let s in clashoo_uci) + if (s.type == type_name) + push(out, s); + return out; +} + +function opt_bool(v, def) { + if (v == null || v == '') + return def; + return v === true || v == '1' || v == 'true' || v == 'yes' || v == 'on'; +} + +function normalize_dns_uri(address, protocol, port) { + address = trim_s(address || ''); + protocol = trim_s(protocol || ''); + port = trim_s(port || ''); + if (!s_len(address)) + return ''; + if (find_char(address, ':') >= 0 && find_char(address, '/') >= 0) + return address; + if (protocol == 'none') + protocol = ''; + if (protocol == 'dot') + protocol = 'tls://'; + else if (protocol == 'doh') + protocol = 'https://'; + else if (protocol == 'doq') + protocol = 'quic://'; + if (s_len(protocol) && !starts_with(protocol, 'udp://') && !starts_with(protocol, 'tcp://') && + !starts_with(protocol, 'tls://') && !starts_with(protocol, 'https://') && !starts_with(protocol, 'quic://')) + protocol += '://'; + return protocol + address + (s_len(port) ? ':' + port : ''); +} + +function direct_outbound_tag() { + for (let ob in (cfg.outbounds || [])) + if (ob && ob.type == 'direct' && s_len(ob.tag || '')) + return ob.tag; + return 'DIRECT'; +} + +function dns_server_obj(uri, tag, fallback_type) { + uri = normalize_dns_uri(uri || '', '', ''); + if (!s_len(uri)) + return null; + let scheme = fallback_type || 'udp'; + let rest = uri; + let p = -1; + for (let i = 0; i < s_len(uri) - 2; i++) { + if (s_sub(uri, i, 3) == '://') { + p = i; + break; + } + } + if (p >= 0) { + scheme = s_sub(uri, 0, p); + rest = s_sub(uri, p + 3); + } + if (scheme == 'dot') + scheme = 'tls'; + else if (scheme == 'doh') + scheme = 'https'; + else if (scheme == 'doq') + scheme = 'quic'; + + let path = ''; + let slash = find_char(rest, '/'); + if (slash >= 0) { + path = s_sub(rest, slash); + rest = s_sub(rest, 0, slash); + } + + let server = rest, server_port = null; + let colon = find_last_char(rest, ':'); + if (colon > 0 && find_char(rest, ']') < 0) { + server = s_sub(rest, 0, colon); + let n = +(s_sub(rest, colon + 1)); + if (n === n) + server_port = n; + } + + let obj = { type: scheme, tag: tag, server: server }; + if (server_port != null) + obj.server_port = server_port; + if (path && (scheme == 'https' || scheme == 'h3')) + obj.path = path; + if ((scheme == 'https' || scheme == 'tls' || scheme == 'quic') && tag != 'dns_resolver') + obj.domain_resolver = 'dns_resolver'; + /* sing-box 1.12+ DNS servers detour to the proxy by default, so a server + * resolving its own domain via dns_resolver loops -> "deadline exceeded". + * Force direct/resolver servers to DIRECT; only dns_proxy may use the proxy. */ + if (tag == 'dns_resolver' || tag == 'dns_direct' || tag == 'dns_foreign') + obj.detour = direct_outbound_tag(); + return obj; +} + +function dns_servers_by_role(role) { + let out = []; + for (let s in uci_sections('dnsservers')) { + if (!opt_bool(s.options.enabled, true)) + continue; + if ((s.options.ser_type || 'nameserver') != role) + continue; + let uri = normalize_dns_uri(s.options.ser_address || '', s.options.protocol || '', s.options.ser_port || ''); + if (s_len(uri)) + push(out, uri); + } + return out; +} + +function first_or(arr, fallback) { + return length(arr) ? arr[0] : fallback; +} + +function local_rule_set_path(tag) { + if (!s_len(tag)) + return ''; + let path = '/usr/share/clashoo/ruleset/' + tag + '.srs'; + if (access(path, 'r')) + return path; + if (tag == 'geolocation-cn' || tag == 'cn') { + path = '/usr/share/clashoo/ruleset/geosite-cn.srs'; + if (access(path, 'r')) + return path; + } + return ''; +} + +function keep_remote_rule_set(rs) { + return false; +} + +function normalize_rule_set_url(url) { + url = url || ''; + url = replace(url, /^https:\/\/gh-proxy\.com\//, ''); + let m = match(url, /^https:\/\/raw\.githubusercontent\.com\/([^\/]+)\/([^\/]+)\/([^\/]+)\/(.+)$/); + if (m) + return 'https://cdn.jsdelivr.net/gh/' + m[1] + '/' + m[2] + '@' + m[3] + '/' + m[4]; + m = match(url, /^https:\/\/github\.com\/([^\/]+)\/([^\/]+)\/raw\/refs\/heads\/([^\/]+)\/(.+)$/); + if (m) + return 'https://cdn.jsdelivr.net/gh/' + m[1] + '/' + m[2] + '@' + m[3] + '/' + m[4]; + return url; +} + +function has_route_rule_set(tag) { + for (let rs in (cfg.route || {}).rule_set || []) + if (rs && rs.tag == tag) + return true; + return false; +} + +function add_remote_rule_set(tag, url) { + cfg.route = cfg.route || {}; + cfg.route.rule_set = cfg.route.rule_set || []; + if (has_route_rule_set(tag)) + return; + push(cfg.route.rule_set, { + tag: tag, + type: 'remote', + format: 'binary', + url: url + }); +} + +function array_has_value(arr, val) { + if (type(arr) != 'array') + return false; + for (let item in arr) + if (item == val) + return true; + return false; +} + +let dnsmasq_uid = null; +let _dnsmasq_uid_pipe = popen("id -u dnsmasq 2>/dev/null"); +if (_dnsmasq_uid_pipe) { + let value = trim_s(_dnsmasq_uid_pipe.read('all')); + _dnsmasq_uid_pipe.close(); + if (match(value, /^[0-9]+$/)) + dnsmasq_uid = +value; +} + +function ensure_tun_dnsmasq_exclude(ib) { + if (dnsmasq_uid == null) + return; + let excludes = ib.exclude_uid; + if (type(excludes) != 'array') + excludes = excludes != null ? [ excludes ] : []; + if (!array_has_value(excludes, dnsmasq_uid)) + push(excludes, dnsmasq_uid); + ib.exclude_uid = excludes; +} + +function ensure_tun_cn_exclude(ib) { + let excludes = ib.route_exclude_address_set; + if (type(excludes) != 'array') + excludes = excludes ? [ excludes ] : []; + if (!array_has_value(excludes, 'cn-ip')) + push(excludes, 'cn-ip'); + ib.route_exclude_address_set = excludes; +} + +function prune_tun_cn_exclude_if_missing() { + if (has_route_rule_set('cn-ip')) + return; + for (let ib in cfg.inbounds || []) { + if (!ib || ib.type != 'tun') + continue; + let excludes = []; + for (let item in ib.route_exclude_address_set || []) + if (item != 'cn-ip') + push(excludes, item); + if (length(excludes)) + ib.route_exclude_address_set = excludes; + else + delete ib.route_exclude_address_set; + } +} + +function matcher_rule(matcher, server_tag) { + let r = { server: server_tag }; + if (starts_with(matcher, 'geosite:')) + r.rule_set = s_sub(matcher, 8); + else if (starts_with(matcher, 'rule_set:')) + r.rule_set = s_sub(matcher, 9); + else if (starts_with(matcher, 'domain:')) + r.domain = s_sub(matcher, 7); + else if (starts_with(matcher, 'domain-suffix:')) + r.domain_suffix = s_sub(matcher, 14); + else + r.domain_suffix = matcher; + return r; +} + +function apply_dns_from_uci() { + cfg.dns = cfg.dns || {}; + /* strip mihomo-style DNS fields (enable/ipv6/listen/fake-ip-filter/nameserver...) + * that leak in from YAML conversion; sing-box rejects them (Fatal). */ + let _mihomo_dns_fields = ['enable', 'ipv6', 'listen', 'fake-ip-filter', 'fake-ip-range', + 'enhanced-mode', 'nameserver', 'fallback', 'fallback-filter', + 'use-hosts', 'default-nameserver', 'proxy-server-nameserver', + 'direct-nameserver', 'nameserver-policy']; + for (let _i = 0; _i < length(_mihomo_dns_fields); _i++) + delete cfg.dns[_mihomo_dns_fields[_i]]; + /* strip mihomo-style experimental fields */ + let _mihomo_exp = ['sniff-tls-sni', 'sniff', 'sniffer']; + for (let _me = 0; _me < length(_mihomo_exp); _me++) + if (cfg.experimental && cfg.experimental[_mihomo_exp[_me]] != null) + delete cfg.experimental[_mihomo_exp[_me]]; + /* strip non-sing-box root-level fields */ + let _mihomo_root = ['clash-for-android', 'cfw-bypass', 'sniffer', 'profile', + 'geodata-mode', 'geodata-loader', 'geox-url', 'geo-auto-update', + 'geo-update-interval', 'tun', 'ipv6', 'interface-name', + 'port', 'socks-port', 'mixed-port', 'redir-port', 'tproxy-port', 'mode', 'allow-lan', 'log-level', 'external-controller', 'secret', 'bind-address', 'routing-mark', 'find-process-mode', 'tcp-concurrent', 'unified-delay', + 'keep-alive-interval', 'keep-alive-idle', 'disable-keep-alive']; + /* strip subconverter note fields and non-standard keys */ + let _subconv_fields = ['_note', '_sub_url', 'hosts', 'script', 'enable', 'fake-ip-filter', 'fake-ip-range']; + for (let _sf = 0; _sf < length(_subconv_fields); _sf++) + if (cfg[_subconv_fields[_sf]] != null) + delete cfg[_subconv_fields[_sf]]; + for (let _mr = 0; _mr < length(_mihomo_root); _mr++) + if (cfg[_mihomo_root[_mr]] != null) + delete cfg[_mihomo_root[_mr]]; + let bootstrap = uci_list('default_nameserver'); + if (!length(bootstrap)) + bootstrap = uci_list('defaul_nameserver'); + let resolver_uri = first_or(bootstrap, '223.5.5.5'); + let direct_uri = first_or(dns_servers_by_role('direct-nameserver'), first_or(dns_servers_by_role('proxy-server-nameserver'), 'https://doh.pub/dns-query')); + let proxy_uri = first_or(dns_servers_by_role('nameserver'), first_or(dns_servers_by_role('fallback'), 'https://1.1.1.1/dns-query')); + + let servers = []; + push(servers, dns_server_obj(resolver_uri, 'dns_resolver', 'udp')); + push(servers, dns_server_obj(direct_uri, 'dns_direct', 'udp')); + push(servers, dns_server_obj(proxy_uri, 'dns_proxy', 'tls')); + + let enhanced = uci_opt('enhanced_mode', 'fake-ip'); + if (enhanced == 'fake-ip') { + let fake_range = uci_opt('fake_ip_range', '198.18.0.1/16'); + push(servers, { + type: 'fakeip', + tag: 'dns_fakeip', + inet4_range: fake_range || '198.18.0.1/16', + inet6_range: 'fc00::/18' + }); + } + + let rules = []; + push(servers, { type: 'udp', tag: 'dns_foreign', server: '1.1.1.1' }); + if (enhanced == 'fake-ip') { + push(rules, { + rule_set: 'geolocation-!cn', + server: 'dns_fakeip' + }); + } + + let clean_servers = []; + for (let s in servers) + if (s) + push(clean_servers, s); + cfg.dns.servers = clean_servers; + cfg.dns.rules = rules; + + /* Keep bootstrap direct; proxy unmatched queries in strict mode. */ + if (opt_bool(uci_opt('dns_leak_protect', '0'), false)) { + cfg.dns.final = 'dns_proxy'; + if (!opt_bool(uci_opt('ipv6_proxy', '0'), false)) + unshift(cfg.dns.rules, { query_type: ['AAAA'], action: 'reject', method: 'drop' }); + } else + cfg.dns.final = 'dns_direct'; + + let ecs = trim_s(uci_opt('dns_ecs', '')); + if (s_len(ecs)) + cfg.dns.client_subnet = ecs; + else + delete cfg.dns.client_subnet; + + if (opt_bool(uci_opt('singbox_independent_cache', '0'), false)) + cfg.dns.independent_cache = true; + else + delete cfg.dns.independent_cache; + + /* sing-box 1.14 requires this (else Fatal); dns_resolver is DIRECT so first-start node lookups use direct DNS */ + cfg.route.default_domain_resolver = 'dns_resolver'; + } + +let inbounds = cfg.inbounds || []; +let normalized = []; +let has_redirect = false; +let has_tproxy = false; +let has_mixed = false; +let has_tun = false; +let has_dns_in = false; +let wants_tun = has_tun_device && (uci_opt('tcp_mode', '') == 'tun' || uci_opt('udp_mode', '') == 'tun'); +let tun_stack = uci_opt('stack', 'mixed') || 'mixed'; + +if (wants_tun) { + add_remote_rule_set('cn-ip', + 'https://github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo/geoip/cn.srs'); +} + +for (let ib in inbounds) { + if (!ib) + continue; + + if (ib.type == 'tun' || ib.tag == 'tun-in') { + /* Keep tun inbound only when the selected proxy mode actually uses TUN. */ + if (wants_tun && !has_tun) { + ib.type = 'tun'; + ib.tag = ib.tag || 'tun-in'; + if (!ib.address) + ib.address = [ '172.19.0.1/30', 'fdfe:dcba:9876::1/126' ]; + ib.auto_route = true; + ib.auto_redirect = true; + ib.auto_redirect_input_mark = '0x2023'; + ib.auto_redirect_output_mark = '0x2024'; + ib.strict_route = true; + ib.stack = tun_stack; + ensure_tun_dnsmasq_exclude(ib); + ensure_tun_cn_exclude(ib); + push(normalized, ib); + has_tun = true; + } + continue; + } + + if (ib.tag == 'redirect-in' || ib.type == 'redirect') { + if (has_redirect) + continue; + ib.type = 'redirect'; + ib.tag = 'redirect-in'; + ib.listen = '0.0.0.0'; + ib.listen_port = redir_port; + has_redirect = true; + push(normalized, ib); + continue; + } + + if (ib.tag == 'tproxy-in' || ib.type == 'tproxy') { + if (has_tproxy) + continue; + ib.type = 'tproxy'; + ib.tag = 'tproxy-in'; + ib.listen = '0.0.0.0'; + ib.listen_port = tproxy_port; + ib.network = 'udp'; + has_tproxy = true; + push(normalized, ib); + continue; + } + + if (ib.tag == 'mixed-in' || ib.type == 'mixed') { + if (has_mixed) + continue; + ib.type = 'mixed'; + ib.tag = 'mixed-in'; + ib.listen = '0.0.0.0'; + ib.listen_port = mixed_port; + has_mixed = true; + push(normalized, ib); + continue; + } + + if (ib.tag == 'dns-in') { + if (has_dns_in) + continue; + ib.type = 'direct'; + ib.tag = 'dns-in'; + ib.listen = '0.0.0.0'; + ib.listen_port = dns_port; + has_dns_in = true; + push(normalized, ib); + continue; + } + + push(normalized, ib); +} + +if (!has_redirect) { + push(normalized, { + type: 'redirect', + tag: 'redirect-in', + listen: '0.0.0.0', + listen_port: redir_port + }); +} + +if (!has_mixed) { + push(normalized, { + type: 'mixed', + tag: 'mixed-in', + listen: '0.0.0.0', + listen_port: mixed_port + }); +} + +if (wants_tun && !has_tun) { + let tun_ib = { + type: 'tun', + tag: 'tun-in', + address: [ '172.19.0.1/30', 'fdfe:dcba:9876::1/126' ], + auto_route: true, + auto_redirect: true, + auto_redirect_input_mark: '0x2023', + auto_redirect_output_mark: '0x2024', + strict_route: true, + stack: tun_stack + }; + ensure_tun_dnsmasq_exclude(tun_ib); + ensure_tun_cn_exclude(tun_ib); + push(normalized, tun_ib); +} + +if (!has_tproxy) { + push(normalized, { + type: 'tproxy', + tag: 'tproxy-in', + listen: '0.0.0.0', + listen_port: tproxy_port, + network: 'udp' + }); +} + +if (!has_dns_in) { + push(normalized, { + type: 'direct', + tag: 'dns-in', + listen: '0.0.0.0', + listen_port: dns_port + }); +} + +cfg.inbounds = normalized; + +for (let ob in (cfg.outbounds || [])) { + if (!ob || type(ob) != 'object') + continue; + + let t = ob.type || ''; + if (t == 'selector' || t == 'urltest' || t == 'fallback' || t == 'load_balance' || t == 'dns' || t == 'block') + continue; + + /* Clash SS obfs is plugin=obfs + plugin-opts object; sing-box wants + * plugin=obfs-local + "obfs=http;obfs-host=..." string. */ + if (ob.plugin == 'obfs') + ob.plugin = 'obfs-local'; + if (ob.plugin_opts != null && type(ob.plugin_opts) == 'object') { + let _parts = []; + let _map = { mode: 'obfs', host: 'obfs-host', uri: 'obfs-uri' }; + for (let _k in ob.plugin_opts) { + let _v = ob.plugin_opts[_k]; + if (type(_v) == 'object' || type(_v) == 'array') continue; + push(_parts, (_map[_k] || _k) + '=' + _v); + } + ob.plugin_opts = join(';', _parts); + } + if (ob.plugin != null && ob.plugin != 'obfs-local' && ob.plugin != 'v2ray-plugin' && ob.plugin != 'shadow-tls') { + delete ob.plugin; + delete ob.plugin_opts; + } + + /* Force ipv4_only on DIRECT outbound: most devices are IPv4-only egress, + * a CN domain resolving AAAA stalls on "network is unreachable". */ + if (ob.type == 'direct' && ob.tag == 'DIRECT') + ob.domain_resolver = { server: 'dns_resolver', strategy: 'ipv4_only' }; + + if (wants_tun) { + delete ob.routing_mark; + if (ob.type == 'direct' && ob.tag == 'DIRECT' && default_interface) + ob.bind_interface = ob.bind_interface || default_interface; + } else if (ob.routing_mark == null) + ob.routing_mark = routing_mark; + } + +/* Drop airline pseudo-nodes (Traffic:/Expire:/quota/官网/QQ/etc.) from selector/urltest lists. + * They're real SS/Vmess (kept so the UI reads traffic/expiry) but don't forward; in a + * selector/urltest they win and swallow foreign traffic ("CN ok, foreign out"). */ +let _is_pseudo_tag = function(t) { + if (!t) return false; + return match(t, /^Traffic[::]/) || match(t, /^Expire[::]/) || + match(t, /剩余流量|剩余[::]/) || match(t, /距离下次重置/) || + match(t, /到期(时间|日期)?[::]/) || + match(t, /官网[::]|网站[::]|套餐[::]?|客服[::]/) || + match(t, /QQ[群]?[::]/) || match(t, /Telegram|TG群|官方群/) || + match(t, /续费|订阅地址|流量重置/); +}; +for (let ob in (cfg.outbounds || [])) { + if (!ob || type(ob) != 'object') continue; + let t = ob.type || ''; + if (t != 'selector' && t != 'urltest' && t != 'fallback' && t != 'load_balance') continue; + if (type(ob.outbounds) != 'array') continue; + let cleaned = []; + for (let tag in ob.outbounds) if (!_is_pseudo_tag(tag)) push(cleaned, tag); + ob.outbounds = cleaned; +} + +cfg.route = cfg.route || {}; +cfg.route.rules = cfg.route.rules || []; +let has_dns_hijack = false; +for (let rule in cfg.route.rules) { + if (!rule || type(rule) != 'object') + continue; + if (rule.inbound == 'dns-in' && rule.action == 'hijack-dns') { + has_dns_hijack = true; + break; + } +} +if (!has_dns_hijack) { + unshift(cfg.route.rules, { + inbound: 'dns-in', + action: 'hijack-dns' + }); +} +cfg.route.auto_detect_interface = true; +apply_dns_from_uci(); + +/* DNS leak protection: reject DoT/DoQ (853) to force DNS through the core; after hijack-dns so the dns inbound survives */ +if (opt_bool(uci_opt('dns_leak_protect', '0'), false)) { + let _has_853 = false; + for (let _r in cfg.route.rules) { + if (_r && type(_r) == 'object' && _r.action == 'reject' && _r.port) { + if (type(_r.port) == 'array') { + for (let _p in _r.port) if (_p == 853) { _has_853 = true; break; } + } else if (_r.port == 853) { + _has_853 = true; + } + } + if (_has_853) break; + } + if (!_has_853) { + let _new_rules = []; + let _inserted = false; + for (let _ri = 0; _ri < length(cfg.route.rules); _ri++) { + push(_new_rules, cfg.route.rules[_ri]); + if (!_inserted && cfg.route.rules[_ri] && cfg.route.rules[_ri].action == 'hijack-dns') { + push(_new_rules, { port: [853], action: 'reject' }); + _inserted = true; + } + } + if (!_inserted) unshift(_new_rules, { port: [853], action: 'reject' }); + cfg.route.rules = _new_rules; + } +} + +/* ===== Custom routing rules (UCI: config addtype) -> route.rules, mirrors mihomo iprules.sh ===== + * User "domain/IP -> direct/proxy" rules from LuCI, inserted after the DNS funnel + * (hijack-dns / 853-reject) and before other rules, so overrides win without bypassing it. */ +let _proxy_outbound_tag = function() { + for (let ob in (cfg.outbounds || [])) + if (ob && ob.tag == '🚀 节点选择' && (ob.type == 'selector' || ob.type == 'urltest')) + return ob.tag; + for (let ob in (cfg.outbounds || [])) + if (ob && ob.type == 'selector' && s_len(ob.tag || '') && !_is_pseudo_tag(ob.tag)) + return ob.tag; + return direct_outbound_tag(); +}; +let _load_addtype_rules = function() { + let out = []; + let dtag = direct_outbound_tag(); + let ptag = null; + for (let s in uci_sections('addtype')) { + let o = s.options || {}; + let addr = trim_s(o.ipaaddr || ''); + let rtype = trim_s(o.type || ''); + let pg = trim_s(o.pgroup || ''); + if (!s_len(addr) || !s_len(rtype) || !s_len(pg)) + continue; + let r = {}; + if (rtype == 'DOMAIN') + r.domain = addr; + else if (rtype == 'DOMAIN-SUFFIX') + r.domain_suffix = addr; + else if (rtype == 'DOMAIN-KEYWORD') + r.domain_keyword = addr; + else if (rtype == 'IP-CIDR' || rtype == 'IP-CIDR6') + r.ip_cidr = addr; + else + continue; + if (pg == 'DIRECT') + r.outbound = dtag; + else { + if (ptag == null) + ptag = _proxy_outbound_tag(); + r.outbound = ptag; + } + push(out, r); + } + return out; +}; +let _addtype_rules = _load_addtype_rules(); +if (length(_addtype_rules)) { + let _nr = []; + let _ins = false; + for (let _i = 0; _i < length(cfg.route.rules); _i++) { + let _ru = cfg.route.rules[_i]; + let _dns_front = _ru && type(_ru) == 'object' && + (_ru.action == 'hijack-dns' || (_ru.action == 'reject' && _ru.port)); + if (!_ins && !_dns_front) { + for (let _cr in _addtype_rules) + push(_nr, _cr); + _ins = true; + } + push(_nr, _ru); + } + if (!_ins) + for (let _cr in _addtype_rules) + push(_nr, _cr); + cfg.route.rules = _nr; +} + +if (uci_opt('enhanced_mode', 'fake-ip') == 'fake-ip') { + add_remote_rule_set('geolocation-!cn', + 'https://github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo/geosite/geolocation-!cn.srs'); +} + +/* Local .srs -> local; remaining remote rule_sets must keep/get a download_detour. + * CN first-start deadlock: no srs -> fetch rules -> via auto-select -> urltest dials + * the proxy -> resolves its domain -> pulled into an unloaded rule_set by DNS rules -> deadline. + * The srs URLs use a CN-reachable mirror, so download_detour + * must be DIRECT (real direct-outbound tag if any, else 'DIRECT'). */ +let _pick_dl_detour = function() { + if (cfg.outbounds) { + for (let ob in cfg.outbounds) { + if (ob && ob.type == 'direct' && ob.tag) return ob.tag; + } + } + return 'DIRECT'; +}; +let _dl_detour = _pick_dl_detour(); +for (let rs in (cfg.route || {}).rule_set || []) { + if (!rs) continue; + if (rs.type != 'remote') { delete rs.download_detour; continue; } + let path = local_rule_set_path(rs.tag || ''); + if (rs.tag && access(path, 'r')) { + delete rs.url; delete rs.download_detour; rs.type = 'local'; rs.path = path; + continue; + } + if (keep_remote_rule_set(rs) && rs.url) { + rs.url = normalize_rule_set_url(rs.url); + rs.download_detour = _dl_detour; + continue; + } + /* No local cache -> skip this remote rule_set so a failed download doesn't block start. + * Rule-sets are fetched to /usr/share/clashoo/ruleset/ once proxied. */ + continue; + +} +/* filter out remote rule_sets with no local cache */ +let _clean_rs = []; +for (let _rsi = 0; _rsi < length(cfg.route.rule_set); _rsi++) { + let _rs = cfg.route.rule_set[_rsi]; + if (!_rs) continue; + if (_rs.type == 'remote' && _rs.url != null) { + let _local_path = local_rule_set_path(_rs.tag || ''); + if (!_rs.tag || !access(_local_path, 'r')) + if (keep_remote_rule_set(_rs) && _rs.url) { + _rs.url = normalize_rule_set_url(_rs.url); + _rs.download_detour = _dl_detour; + push(_clean_rs, _rs); + continue; + } else { + continue; /* no local cache -> drop from final list */ + } + /* has local cache -> convert to local */ + delete _rs.url; delete _rs.download_detour; _rs.type = 'local'; _rs.path = _local_path; + } + push(_clean_rs, _rs); +} +cfg.route.rule_set = _clean_rs; +prune_tun_cn_exclude_if_missing(); + + /* drop DNS/route rules referencing a removed rule_set, else sing-box FATAL: rule-set not found */ + let _existing_tags = {}; + for (let _rs in cfg.route.rule_set || []) if (_rs && _rs.tag) _existing_tags[_rs.tag] = true; + let _rule_has_ref = function(_r) { + if (!_r || type(_r) != 'object') return false; + if (_r.rule_set) { + if (type(_r.rule_set) == 'array') { for (let _t in _r.rule_set) if (!_existing_tags[_t]) return false; } + else if (!_existing_tags[_r.rule_set]) return false; + } + return true; + }; + /* clean dns.rules */ + if (cfg.dns && type(cfg.dns.rules) == 'array') { + let _clean_dns_rules = []; + for (let _dr in cfg.dns.rules) if (_rule_has_ref(_dr)) push(_clean_dns_rules, _dr); + cfg.dns.rules = _clean_dns_rules; + } + /* clean route.rules */ + if (cfg.route && type(cfg.route.rules) == 'array') { + let _clean_rt_rules = []; + for (let _rr in cfg.route.rules) if (_rule_has_ref(_rr)) push(_clean_rt_rules, _rr); + cfg.route.rules = _clean_rt_rules; + } + cfg.experimental = cfg.experimental || {}; +cfg.experimental.clash_api = cfg.experimental.clash_api || {}; +cfg.experimental.clash_api.external_controller = '0.0.0.0:' + dash_port; +cfg.experimental.clash_api.external_ui = '/etc/clashoo/dashboard'; +cfg.experimental.clash_api.secret = dash_secret; + +cfg.experimental.cache_file = { + enabled: true, + store_fakeip: true +}; + +/* OpenWrt already owns system time sync. Keep sing-box NTP disabled by + * default to avoid noisy IPv6 UDP/123 failures on IPv4-only routers. */ +cfg.ntp = cfg.ntp || {}; +cfg.ntp.enabled = false; + +cfg.log = cfg.log || {}; +cfg.log.output = '/var/log/clashoo/core.log'; +if (!cfg.log.level) + cfg.log.level = 'info'; + +if (writefile(path, sprintf('%J', cfg)) === null) { + print("write failed\n"); + exit(1); +} + +print("normalized\n"); diff --git a/clashoo/files/usr/share/clashoo/lib/templates/default.json b/clashoo/files/usr/share/clashoo/lib/templates/default.json new file mode 100644 index 00000000..84f1ee22 --- /dev/null +++ b/clashoo/files/usr/share/clashoo/lib/templates/default.json @@ -0,0 +1,138 @@ +{ + "log": { + "level": "info", + "timestamp": true + }, + "dns": { + "servers": [ + { "type": "tcp", "tag": "dns_proxy", "server": "1.1.1.1", "domain_resolver": "dns_resolver" }, + { "type": "https", "tag": "dns_direct", "server": "dns.alidns.com", "domain_resolver": "dns_resolver" }, + { "type": "udp", "tag": "dns_resolver", "server": "223.5.5.5" }, + { "type": "fakeip", "tag": "dns_fakeip", "inet4_range": "198.18.0.0/15", "inet6_range": "fc00::/18" } + ], + "rules": [ + { "rule_set": "geolocation-!cn", "query_type": ["A", "AAAA", "CNAME"], "server": "dns_fakeip" }, + { "query_type": ["A", "AAAA", "CNAME"], "invert": true, "action": "reject" } + ], + "final": "dns_direct" + }, + "ntp": { + "enabled": false, + "server": "time.apple.com", + "server_port": 123, + "interval": "30m" + }, + "inbounds": [ + { "type": "mixed", "tag": "mixed-in", "listen": "0.0.0.0", "listen_port": 2080 }, + { "type": "tun", "tag": "tun-in", "address": ["172.19.0.1/30"], "auto_route": true, "auto_redirect": true, "strict_route": true, "stack": "mixed", "route_exclude_address_set": ["cn-ip"] } + ], + "outbounds": [ + { + "type": "selector", "tag": "🚀 节点选择", + "outbounds": ["🌍 全部节点","♻️ 自动选择","♻️ 香港自动","♻️ 日本自动","♻️ 美国自动","♻️ 新加坡自动","♻️ 集合自动","🇭🇰 香港节点","🇯🇵 日本节点","🇺🇲 美国节点","🇸🇬 新加坡节点","🇺🇳 集合节点","DIRECT"], + "default": "🌍 全部节点" + }, + { "type": "selector", "tag": "▶️ YouTube", "outbounds": ["🚀 节点选择","♻️ 香港自动","♻️ 日本自动","♻️ 美国自动","♻️ 新加坡自动","♻️ 集合自动","♻️ 自动选择","🇭🇰 香港节点","🇯🇵 日本节点","🇺🇲 美国节点","🇸🇬 新加坡节点","🇺🇳 集合节点","🌍 全部节点","DIRECT"] }, + { "type": "selector", "tag": "🔍 Google", "outbounds": ["🚀 节点选择","♻️ 香港自动","♻️ 日本自动","♻️ 美国自动","♻️ 新加坡自动","♻️ 集合自动","♻️ 自动选择","🇭🇰 香港节点","🇯🇵 日本节点","🇺🇲 美国节点","🇸🇬 新加坡节点","🇺🇳 集合节点","🌍 全部节点","DIRECT"] }, + { "type": "selector", "tag": "🔮 OpenAI", "outbounds": ["🚀 节点选择","♻️ 香港自动","♻️ 日本自动","♻️ 美国自动","♻️ 新加坡自动","♻️ 集合自动","♻️ 自动选择","🇭🇰 香港节点","🇯🇵 日本节点","🇺🇲 美国节点","🇸🇬 新加坡节点","🇺🇳 集合节点","🌍 全部节点","DIRECT"] }, + { "type": "selector", "tag": "🧠 Claude", "outbounds": ["🚀 节点选择","♻️ 香港自动","♻️ 日本自动","♻️ 美国自动","♻️ 新加坡自动","♻️ 集合自动","♻️ 自动选择","🇭🇰 香港节点","🇯🇵 日本节点","🇺🇲 美国节点","🇸🇬 新加坡节点","🇺🇳 集合节点","🌍 全部节点","DIRECT"] }, + { "type": "selector", "tag": "🤖 Gemini", "outbounds": ["🚀 节点选择","♻️ 香港自动","♻️ 日本自动","♻️ 美国自动","♻️ 新加坡自动","♻️ 集合自动","♻️ 自动选择","🇭🇰 香港节点","🇯🇵 日本节点","🇺🇲 美国节点","🇸🇬 新加坡节点","🇺🇳 集合节点","🌍 全部节点","DIRECT"] }, + { "type": "selector", "tag": "🐦 Twitter", "outbounds": ["🚀 节点选择","♻️ 香港自动","♻️ 日本自动","♻️ 美国自动","♻️ 新加坡自动","♻️ 集合自动","♻️ 自动选择","🇭🇰 香港节点","🇯🇵 日本节点","🇺🇲 美国节点","🇸🇬 新加坡节点","🇺🇳 集合节点","🌍 全部节点","DIRECT"] }, + { "type": "selector", "tag": "📦 GitHub", "outbounds": ["🚀 节点选择","♻️ 香港自动","♻️ 日本自动","♻️ 美国自动","♻️ 新加坡自动","♻️ 集合自动","♻️ 自动选择","🇭🇰 香港节点","🇯🇵 日本节点","🇺🇲 美国节点","🇸🇬 新加坡节点","🇺🇳 集合节点","🌍 全部节点","DIRECT"] }, + { "type": "selector", "tag": "Ⓜ️ Microsoft", "outbounds": ["🚀 节点选择","♻️ 香港自动","♻️ 日本自动","♻️ 美国自动","♻️ 新加坡自动","♻️ 集合自动","♻️ 自动选择","🇭🇰 香港节点","🇯🇵 日本节点","🇺🇲 美国节点","🇸🇬 新加坡节点","🇺🇳 集合节点","🌍 全部节点","DIRECT"] }, + { "type": "selector", "tag": "🎵 TikTok", "outbounds": ["🚀 节点选择","♻️ 香港自动","♻️ 日本自动","♻️ 美国自动","♻️ 新加坡自动","♻️ 集合自动","♻️ 自动选择","🇭🇰 香港节点","🇯🇵 日本节点","🇺🇲 美国节点","🇸🇬 新加坡节点","🇺🇳 集合节点","🌍 全部节点","DIRECT"] }, + { "type": "selector", "tag": "📺 Netflix", "outbounds": ["🚀 节点选择","♻️ 香港自动","♻️ 日本自动","♻️ 美国自动","♻️ 新加坡自动","♻️ 集合自动","♻️ 自动选择","🇭🇰 香港节点","🇯🇵 日本节点","🇺🇲 美国节点","🇸🇬 新加坡节点","🇺🇳 集合节点","🌍 全部节点","DIRECT"] }, + { "type": "selector", "tag": "🎬 流媒体", "outbounds": ["🚀 节点选择","♻️ 香港自动","♻️ 日本自动","♻️ 美国自动","♻️ 新加坡自动","♻️ 集合自动","♻️ 自动选择","🇭🇰 香港节点","🇯🇵 日本节点","🇺🇲 美国节点","🇸🇬 新加坡节点","🇺🇳 集合节点","🌍 全部节点","DIRECT"] }, + { "type": "selector", "tag": "💳 PayPal", "outbounds": ["🚀 节点选择","♻️ 香港自动","♻️ 日本自动","♻️ 美国自动","♻️ 新加坡自动","♻️ 集合自动","♻️ 自动选择","🇭🇰 香港节点","🇯🇵 日本节点","🇺🇲 美国节点","🇸🇬 新加坡节点","🇺🇳 集合节点","🌍 全部节点","DIRECT"] }, + { "type": "selector", "tag": "📲 电报消息", "outbounds": ["🚀 节点选择","♻️ 香港自动","♻️ 日本自动","♻️ 美国自动","♻️ 新加坡自动","♻️ 集合自动","♻️ 自动选择","🇭🇰 香港节点","🇯🇵 日本节点","🇺🇲 美国节点","🇸🇬 新加坡节点","🇺🇳 集合节点","🌍 全部节点","DIRECT"] }, + { "type": "selector", "tag": "🍏 苹果服务", "outbounds": ["DIRECT","🚀 节点选择","♻️ 自动选择"] }, + { "type": "selector", "tag": "🎯 全球直连", "outbounds": ["DIRECT","🚀 节点选择","♻️ 自动选择"] }, + { "type": "selector", "tag": "🏠 私有网络", "outbounds": ["DIRECT","🚀 节点选择","♻️ 自动选择"] }, + { "type": "selector", "tag": "🌐 非中国", "outbounds": ["🚀 节点选择","♻️ 香港自动","♻️ 日本自动","♻️ 美国自动","♻️ 新加坡自动","♻️ 集合自动","♻️ 自动选择","🇭🇰 香港节点","🇯🇵 日本节点","🇺🇲 美国节点","🇸🇬 新加坡节点","🇺🇳 集合节点","🌍 全部节点","DIRECT"] }, + { "type": "selector", "tag": "🐟 漏网之鱼", "outbounds": ["🚀 节点选择","♻️ 香港自动","♻️ 日本自动","♻️ 美国自动","♻️ 新加坡自动","♻️ 集合自动","♻️ 自动选择","🇭🇰 香港节点","🇯🇵 日本节点","🇺🇲 美国节点","🇸🇬 新加坡节点","🇺🇳 集合节点","🌍 全部节点","DIRECT"] }, + + { "type": "selector", "tag": "🇭🇰 香港节点", "outbounds": ["__NODES_HK__"] }, + { "type": "selector", "tag": "🇯🇵 日本节点", "outbounds": ["__NODES_JP__"] }, + { "type": "selector", "tag": "🇺🇲 美国节点", "outbounds": ["__NODES_US__"] }, + { "type": "selector", "tag": "🇸🇬 新加坡节点","outbounds": ["__NODES_SG__"] }, + { "type": "selector", "tag": "🇺🇳 集合节点", "outbounds": ["__NODES_OTHER__"] }, + { "type": "selector", "tag": "🌍 全部节点", "outbounds": ["__NODES__"] }, + + { "type": "urltest", "tag": "♻️ 香港自动", "outbounds": ["__NODES_HK__"], "url": "https://www.gstatic.com/generate_204", "interval": "5m", "tolerance": 50, "interrupt_exist_connections": true }, + { "type": "urltest", "tag": "♻️ 日本自动", "outbounds": ["__NODES_JP__"], "url": "https://www.gstatic.com/generate_204", "interval": "5m", "tolerance": 50, "interrupt_exist_connections": true }, + { "type": "urltest", "tag": "♻️ 美国自动", "outbounds": ["__NODES_US__"], "url": "https://www.gstatic.com/generate_204", "interval": "5m", "tolerance": 50, "interrupt_exist_connections": true }, + { "type": "urltest", "tag": "♻️ 新加坡自动", "outbounds": ["__NODES_SG__"], "url": "https://www.gstatic.com/generate_204", "interval": "5m", "tolerance": 50, "interrupt_exist_connections": true }, + { "type": "urltest", "tag": "♻️ 集合自动", "outbounds": ["__NODES_OTHER__"], "url": "https://www.gstatic.com/generate_204", "interval": "5m", "tolerance": 50, "interrupt_exist_connections": true }, + { "type": "urltest", "tag": "♻️ 自动选择", "outbounds": ["__NODES__"], "url": "https://www.gstatic.com/generate_204", "interval": "10m", "tolerance": 50, "interrupt_exist_connections": true }, + + { "type": "direct", "tag": "DIRECT" } + ], + "route": { + "default_domain_resolver": "dns_resolver", + "rule_set": [ + { "tag": "openai", "type": "remote", "format": "binary", "url": "https://github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo/geosite/openai.srs", "download_detour": "DIRECT" }, + { "tag": "anthropic", "type": "remote", "format": "binary", "url": "https://github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo/geosite/anthropic.srs", "download_detour": "DIRECT" }, + { "tag": "google-gemini", "type": "remote", "format": "binary", "url": "https://github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo/geosite/google-gemini.srs", "download_detour": "DIRECT" }, + { "tag": "youtube", "type": "remote", "format": "binary", "url": "https://github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo/geosite/youtube.srs", "download_detour": "DIRECT" }, + { "tag": "google", "type": "remote", "format": "binary", "url": "https://github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo/geosite/google.srs", "download_detour": "DIRECT" }, + { "tag": "twitter", "type": "remote", "format": "binary", "url": "https://github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo/geosite/twitter.srs", "download_detour": "DIRECT" }, + { "tag": "github", "type": "remote", "format": "binary", "url": "https://github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo/geosite/github.srs", "download_detour": "DIRECT" }, + { "tag": "gitlab", "type": "remote", "format": "binary", "url": "https://github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo/geosite/gitlab.srs", "download_detour": "DIRECT" }, + { "tag": "microsoft", "type": "remote", "format": "binary", "url": "https://github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo/geosite/microsoft.srs", "download_detour": "DIRECT" }, + { "tag": "tiktok", "type": "remote", "format": "binary", "url": "https://github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo/geosite/tiktok.srs", "download_detour": "DIRECT" }, + { "tag": "netflix", "type": "remote", "format": "binary", "url": "https://github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo/geosite/netflix.srs", "download_detour": "DIRECT" }, + { "tag": "hulu", "type": "remote", "format": "binary", "url": "https://github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo/geosite/hulu.srs", "download_detour": "DIRECT" }, + { "tag": "disney", "type": "remote", "format": "binary", "url": "https://github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo/geosite/disney.srs", "download_detour": "DIRECT" }, + { "tag": "hbo", "type": "remote", "format": "binary", "url": "https://github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo/geosite/hbo.srs", "download_detour": "DIRECT" }, + { "tag": "amazon", "type": "remote", "format": "binary", "url": "https://github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo/geosite/amazon.srs", "download_detour": "DIRECT" }, + { "tag": "bahamut", "type": "remote", "format": "binary", "url": "https://github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo/geosite/bahamut.srs", "download_detour": "DIRECT" }, + { "tag": "paypal", "type": "remote", "format": "binary", "url": "https://github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo/geosite/paypal.srs", "download_detour": "DIRECT" }, + { "tag": "apple", "type": "remote", "format": "binary", "url": "https://github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo/geosite/apple.srs", "download_detour": "DIRECT" }, + { "tag": "geolocation-cn", "type": "remote", "format": "binary", "url": "https://github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo/geosite/geolocation-cn.srs", "download_detour": "DIRECT" }, + { "tag": "cn", "type": "remote", "format": "binary", "url": "https://github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo/geosite/cn.srs", "download_detour": "DIRECT" }, + { "tag": "geolocation-!cn", "type": "remote", "format": "binary", "url": "https://github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo/geosite/geolocation-!cn.srs", "download_detour": "DIRECT" }, + { "tag": "google-ip", "type": "remote", "format": "binary", "url": "https://github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo/geoip/google.srs", "download_detour": "DIRECT" }, + { "tag": "private-ip", "type": "remote", "format": "binary", "url": "https://github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo/geoip/private.srs", "download_detour": "DIRECT" }, + { "tag": "cn-ip", "type": "remote", "format": "binary", "url": "https://github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo/geoip/cn.srs", "download_detour": "DIRECT" }, + { "tag": "telegram-ip", "type": "remote", "format": "binary", "url": "https://github.com/MetaCubeX/meta-rules-dat/raw/refs/heads/sing/geo/geoip/telegram.srs", "download_detour": "DIRECT" } + ], + "rules": [ + { "action": "sniff" }, + { "clash_mode": "direct", "outbound": "DIRECT" }, + { "clash_mode": "global", "outbound": "♻️ 自动选择" }, + { "protocol": "dns", "action": "hijack-dns" }, + { "rule_set": "openai", "outbound": "🔮 OpenAI" }, + { "rule_set": "anthropic", "outbound": "🧠 Claude" }, + { "rule_set": "google-gemini", "outbound": "🤖 Gemini" }, + { "rule_set": "youtube", "outbound": "▶️ YouTube" }, + { "rule_set": "google", "outbound": "🔍 Google" }, + { "rule_set": "twitter", "outbound": "🐦 Twitter" }, + { "rule_set": "tiktok", "outbound": "🎵 TikTok" }, + { "rule_set": "netflix", "outbound": "📺 Netflix" }, + { "rule_set": ["hulu", "disney", "hbo", "amazon", "bahamut"], "outbound": "🎬 流媒体" }, + { "rule_set": "paypal", "outbound": "💳 PayPal" }, + { "rule_set": "microsoft", "outbound": "Ⓜ️ Microsoft" }, + { "rule_set": "apple", "outbound": "🍏 苹果服务" }, + { "rule_set": ["github", "gitlab"], "outbound": "📦 GitHub" }, + { "rule_set": "telegram-ip", "outbound": "📲 电报消息" }, + { "rule_set": "private-ip", "outbound": "🏠 私有网络" }, + { "rule_set": ["geolocation-cn", "cn"], "outbound": "🎯 全球直连" }, + { "rule_set": "cn-ip", "outbound": "🎯 全球直连" }, + { "domain_suffix": ["cn"], "outbound": "🎯 全球直连" }, + { "rule_set": "geolocation-!cn", "outbound": "🌐 非中国" }, + { "rule_set": "google-ip", "outbound": "🔍 Google" } + ], + "auto_detect_interface": true, + "final": "🐟 漏网之鱼" + }, + "experimental": { + "cache_file": { "enabled": true, "store_fakeip": true }, + "clash_api": { + "external_controller": "0.0.0.0:9090", + "external_ui": "dashboard", + "secret": "clashoo", + "external_ui_download_url": "https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip", + "external_ui_download_detour": "DIRECT" + } + } +} diff --git a/clashoo/files/usr/share/clashoo/lib/yaml2singbox.uc b/clashoo/files/usr/share/clashoo/lib/yaml2singbox.uc new file mode 100644 index 00000000..37e40c76 --- /dev/null +++ b/clashoo/files/usr/share/clashoo/lib/yaml2singbox.uc @@ -0,0 +1,468 @@ +#!/usr/bin/ucode +/* yaml2singbox.uc — convert mihomo/clash YAML proxies to sing-box outbounds + * Usage: ucode yaml2singbox.uc [template-json] [json-out] + * Supports: ss, vmess, vless, trojan, hysteria2, tuic. Others are skipped. + * "__NODES__" in the template selector outbounds is expanded to all node tags. */ + +'use strict'; + +import { readfile, writefile, popen } from 'fs'; + +const TPL_DEFAULT = '/usr/share/clashoo/lib/templates/default.json'; +const SUPPORTED = { + 'ss': 'shadowsocks', + 'shadowsocks':'shadowsocks', + 'vmess': 'vmess', + 'vless': 'vless', + 'trojan': 'trojan', + 'hysteria2': 'hysteria2', + 'hy2': 'hysteria2', + 'tuic': 'tuic' +}; + +function logerr(msg) { + warn(sprintf("[yaml2singbox] %s\n", msg)); +} + +function die(msg, code) { + logerr(msg); + exit(code || 1); +} + +/* ---------- YAML read (via yq) ---------- */ +function quote_sh(s) { + /* single-quote for shell; escape embedded quotes */ + return "'" + replace(s, "'", "'\\''") + "'"; +} + +function read_yaml_as_json(path) { + const cmd = sprintf("yq -o=json eval . %s 2>/dev/null", quote_sh(path)); + const h = popen(cmd, "r"); + if (!h) + die(sprintf("popen failed: %s", cmd)); + let buf = "", chunk; + while ((chunk = h.read(65536))) + buf += chunk; + h.close(); + if (!length(buf)) + die(sprintf("yq returned empty for %s (yaml loadable?)", path)); + const data = json(buf); + if (data === null) + die("json parse of yq output failed"); + return data; +} + +/* ---------- helpers ---------- */ +function pick(obj, ...keys) { + for (let k in keys) + if (obj[k] != null) + return obj[k]; + return null; +} + +function tobool(v) { + if (v === true || v === 'true' || v === 1 || v === '1') return true; + if (v === false || v === 'false' || v === 0 || v === '0') return false; + return null; +} + +function toint(v) { + if (v == null) return null; + let n = +v; + return (n === n) ? n : null; /* NaN check */ +} + +function strip_null(obj) { + if (type(obj) !== 'object') return obj; + const out = {}; + for (let k in obj) { + const v = obj[k]; + if (v == null) continue; + if (type(v) === 'object') { + const sv = strip_null(v); + if (length(sv) > 0) out[k] = sv; + } else if (type(v) === 'array') { + const arr = map(v, (x) => (type(x) === 'object') ? strip_null(x) : x); + if (length(arr) > 0) out[k] = arr; + } else { + out[k] = v; + } + } + return out; +} + +/* ---------- TLS block assembly ---------- */ +function build_tls(p) { + const enabled = tobool(p.tls) || (p.sni && length(p.sni) > 0) || (p.servername && length(p.servername) > 0); + if (!enabled) return null; + const tls = { + enabled: true, + server_name: pick(p, 'sni', 'servername'), + insecure: tobool(p['skip-cert-verify']) === true ? true : null, + alpn: p.alpn + }; + const reality = p['reality-opts']; + if (reality && type(reality) === 'object') { + tls.reality = { + enabled: true, + public_key: reality['public-key'], + short_id: reality['short-id'] + }; + } + const client_fp = p['client-fingerprint']; + if (client_fp) + tls.utls = { enabled: true, fingerprint: client_fp }; + return strip_null(tls); +} + +/* ---------- transport (ws / grpc / http) ---------- */ +function build_transport(p) { + const net = p.network; + if (!net || net === 'tcp') return null; + if (net === 'ws') { + const opts = p['ws-opts'] || {}; + return strip_null({ + type: 'ws', + path: opts.path, + headers: opts.headers, + max_early_data: toint(opts['max-early-data']), + early_data_header_name: opts['early-data-header-name'] + }); + } + if (net === 'grpc') { + const opts = p['grpc-opts'] || {}; + return { type: 'grpc', service_name: opts['grpc-service-name'] }; + } + if (net === 'http' || net === 'h2') { + const opts = p['h2-opts'] || p['http-opts'] || {}; + return strip_null({ + type: 'http', + host: opts.host || opts.Host, + path: opts.path + }); + } + if (net === 'httpupgrade') { + const opts = p['http-upgrade-opts'] || {}; + return strip_null({ type: 'httpupgrade', path: opts.path, host: opts.host }); + } + logerr(sprintf("unknown transport network '%s' for node '%s', ignored", net, p.name)); + return null; +} + +/* ---------- per-protocol conversion ---------- */ +function convert_ss(p) { + const plugin = p.plugin, popts = p['plugin-opts'] || {}; + let plugin_opts = null; + if (plugin && type(popts) === 'object') { + /* Field mapping: Clash simple-obfs -> sing-box obfs-local + * Clash: mode=http;host=xxx → sing-box: obfs=http;obfs-host=xxx */ + const FIELD_MAP = { + 'mode': 'obfs', + 'host': 'obfs-host', + 'uri': 'obfs-uri' + }; + const parts = []; + for (let k in popts) { + const v = popts[k]; + if (type(v) === 'object') continue; + push(parts, (FIELD_MAP[k] || k) + '=' + v); + } + plugin_opts = join(';', parts); + } + /* Clash "obfs" maps to sing-box plugin "obfs-local" */ + let sb_plugin = null; + if (plugin === 'obfs') sb_plugin = 'obfs-local'; + else if (plugin === 'v2ray-plugin') sb_plugin = 'v2ray-plugin'; + else if (plugin === 'shadow-tls') sb_plugin = 'shadow-tls'; + else if (plugin) sb_plugin = plugin; + + return strip_null({ + type: 'shadowsocks', + tag: p.name, + server: p.server, + server_port: toint(p.port), + method: p.cipher, + password: p.password, + plugin: sb_plugin, + plugin_opts: plugin_opts + }); +} + +function convert_vmess(p) { + return strip_null({ + type: 'vmess', + tag: p.name, + server: p.server, + server_port: toint(p.port), + uuid: p.uuid, + alter_id: toint(p.alterId) || 0, + security: p.cipher || 'auto', + tls: build_tls(p), + transport: build_transport(p) + }); +} + +function convert_vless(p) { + return strip_null({ + type: 'vless', + tag: p.name, + server: p.server, + server_port: toint(p.port), + uuid: p.uuid, + flow: p.flow, + tls: build_tls(p), + transport: build_transport(p) + }); +} + +function convert_trojan(p) { + return strip_null({ + type: 'trojan', + tag: p.name, + server: p.server, + server_port: toint(p.port), + password: p.password, + tls: build_tls(p) || { enabled: true, server_name: p.sni || p.server }, + transport: build_transport(p) + }); +} + +function convert_hysteria2(p) { + const obfs_pass = p['obfs-password']; + return strip_null({ + type: 'hysteria2', + tag: p.name, + server: p.server, + server_port: toint(p.port), + password: pick(p, 'password', 'auth'), + up_mbps: toint(p.up), + down_mbps: toint(p.down), + obfs: (p.obfs && obfs_pass) ? { type: p.obfs, password: obfs_pass } : null, + tls: build_tls(p) || { enabled: true, server_name: p.sni || p.server } + }); +} + +function convert_tuic(p) { + return strip_null({ + type: 'tuic', + tag: p.name, + server: p.server, + server_port: toint(p.port), + uuid: p.uuid, + password: p.password, + congestion_control: p['congestion-controller'] || p.congestion, + udp_relay_mode: p['udp-relay-mode'], + tls: build_tls(p) || { enabled: true, server_name: p.sni || p.server, alpn: p.alpn } + }); +} + +function convert_proxy(p) { + const sb_type = SUPPORTED[p.type]; + if (!sb_type) { + if (p.type && p.type !== 'select') + logerr(sprintf("skip unsupported type '%s' for '%s'", p.type, p.name)); + return null; + } + if (!p.server || !p.port || !p.name) { + logerr(sprintf("skip proxy missing server/port/name (type=%s)", p.type)); + return null; + } + switch (p.type) { + case 'ss': + case 'shadowsocks': return convert_ss(p); + case 'vmess': return convert_vmess(p); + case 'vless': return convert_vless(p); + case 'trojan': return convert_trojan(p); + case 'hysteria2': + case 'hy2': return convert_hysteria2(p); + case 'tuic': return convert_tuic(p); + } + return null; +} + +/* ---------- dedup (tag must be unique) ---------- */ +function dedupe_tags(nodes) { + const seen = {}; + for (let n in nodes) { + let base = n.tag, t = base, i = 2; + while (seen[t]) { + t = base + '_' + i; + i++; + } + n.tag = t; + seen[t] = true; + } + return nodes; +} + +/* Drop airline pseudo-nodes (Traffic:/Expire:/quota/官网/QQ etc.) from selectors + * and urltests. They're real outbounds (so the UI can read traffic/expiry) but + * don't forward — in a selector/urltest they win (0ms) and swallow all traffic. */ +function is_pseudo_node_tag(tag) { + if (!tag) return false; + const t = '' + tag; + const patterns = [ + /^Traffic[::]/i, + /^Expire[::]/i, + /剩余流量|剩余[::]/, + /距离下次重置/, + /到期(时间|日期)?[::]/, + /官网[::]/, + /网站[::]/, + /套餐[::]?/, + /客服[::]/, + /QQ[群]?[::]/i, + /Telegram|TG群|官方群/i, + /续费|订阅地址|流量重置/, + ]; + for (let re in patterns) if (match(t, re)) return true; + return false; +} + +/* ---------- detect region from tag -> 'HK'/'JP'/'US'/'SG'/'OTHER'/'' ---------- */ +function region_of(tag) { + if (!tag) return ''; + const t = '' + tag; + if (match(t, /港|🇭🇰|HK[^A-Za-z]|[^A-Za-z]HK|^HK$|[Hh]ong[Kk]/)) return 'HK'; + if (match(t, /日|🇯🇵|JP[^A-Za-z]|[^A-Za-z]JP|^JP$|[Jj]apan/)) return 'JP'; + if (match(t, /美|🇺🇸|US[^A-Za-z]|[^A-Za-z]US|^US$|[Uu]nited.?[Ss]tates|[Aa]merica/)) return 'US'; + if (match(t, /新加坡|🇸🇬|SG[^A-Za-z]|[^A-Za-z]SG|^SG$|[Ss]ingapore/)) return 'SG'; + if (match(t, /台湾|台|🇹🇼|TW[^A-Za-z]|[^A-Za-z]TW|^TW$|[Tt]aiwan|韩国|韩|🇰🇷|KR[^A-Za-z]|[^A-Za-z]KR|^KR$|[Kk]orea/)) return 'OTHER'; + return ''; +} + +/* ---------- expand __NODES__/__NODES_XX__ placeholders to real tags (dropping pseudo-nodes) ---------- */ +function expand_node_placeholder(outbounds, node_tags) { + const real_tags = []; + for (let t in node_tags) if (!is_pseudo_node_tag(t)) push(real_tags, t); + + /* bucket by region */ + let tags_hk = [], tags_jp = [], tags_us = [], tags_sg = [], tags_other = []; + for (let t in real_tags) { + const r = region_of(t); + if (r === 'HK') push(tags_hk, t); + else if (r === 'JP') push(tags_jp, t); + else if (r === 'US') push(tags_us, t); + else if (r === 'SG') push(tags_sg, t); + else if (r === 'OTHER') push(tags_other, t); + } + /* when a region has no nodes, fall back to all nodes (avoid empty outbounds) */ + if (!length(tags_hk)) tags_hk = real_tags; + if (!length(tags_jp)) tags_jp = real_tags; + if (!length(tags_us)) tags_us = real_tags; + if (!length(tags_sg)) tags_sg = real_tags; + if (!length(tags_other)) tags_other = real_tags; + + for (let ob in outbounds) { + if (ob.type !== 'selector' && ob.type !== 'urltest') continue; + if (!ob.outbounds || type(ob.outbounds) !== 'array') continue; + const expanded = []; + for (let item in ob.outbounds) { + let list = null; + if (item === '__NODES__') list = real_tags; + else if (item === '__NODES_HK__') list = tags_hk; + else if (item === '__NODES_JP__') list = tags_jp; + else if (item === '__NODES_US__') list = tags_us; + else if (item === '__NODES_SG__') list = tags_sg; + else if (item === '__NODES_OTHER__') list = tags_other; + if (list !== null) { for (let t in list) push(expanded, t); } + else { push(expanded, item); } + } + ob.outbounds = expanded; + } + return outbounds; +} + +/* ---------- proxy-providers ---------- */ +function resolve_providers(yaml) { + if (type(yaml['proxy-providers']) !== 'object') + return []; + const all = []; + for (let name in yaml['proxy-providers']) { + const p = yaml['proxy-providers'][name]; + if (p.type !== 'http' || !p.url) { + logerr(sprintf("provider '%s': skip (not http or no url)", name)); + continue; + } + const cmd = sprintf("wget -q -O- --timeout=20 %s 2>/dev/null | yq -o=json eval . 2>/dev/null", quote_sh(p.url)); + const h = popen(cmd, "r"); + if (!h) { logerr(sprintf("provider '%s': download failed", name)); continue; } + let buf = "", chunk; + while ((chunk = h.read(65536))) buf += chunk; + h.close(); + if (!length(buf)) { logerr(sprintf("provider '%s': empty response", name)); continue; } + const data = json(buf); + if (!data || type(data.proxies) !== 'array') { + logerr(sprintf("provider '%s': no proxies array in response", name)); + continue; + } + logerr(sprintf("provider '%s': %d proxies", name, length(data.proxies))); + for (let px in data.proxies) push(all, px); + } + return all; +} + +/* ---------- main ---------- */ +const yaml_path = ARGV[0]; +const tpl_path = ARGV[1] || TPL_DEFAULT; +const out_path = ARGV[2]; + +if (!yaml_path) + die("usage: ucode yaml2singbox.uc [template-json] [json-out]"); + +const yaml = read_yaml_as_json(yaml_path); + +let proxies = []; +if (type(yaml.proxies) === 'array') + proxies = yaml.proxies; +else + logerr("no inline proxies array, checking proxy-providers..."); + +if (type(yaml['proxy-providers']) === 'object') { + const pproxies = resolve_providers(yaml); + for (let p in pproxies) push(proxies, p); +} + +if (!length(proxies)) + die(sprintf("no proxies found in %s (inline or via providers)", yaml_path)); + +const tpl_raw = readfile(tpl_path); +if (!tpl_raw) + die(sprintf("cannot read template %s", tpl_path)); +const tpl = json(tpl_raw); +if (!tpl || type(tpl.outbounds) !== 'array') + die(sprintf("template %s has no outbounds[]", tpl_path)); + +/* convert proxies */ +const nodes = []; +let skipped = 0; +for (let p in proxies) { + const o = convert_proxy(p); + if (o) push(nodes, o); else skipped++; +} +dedupe_tags(nodes); + +if (!length(nodes)) + die(sprintf("no usable nodes converted from %d proxies (skipped=%d)", length(proxies), skipped)); + +logerr(sprintf("converted=%d skipped=%d", length(nodes), skipped)); + +/* prepend converted nodes to template outbounds */ +const final_outbounds = []; +for (let n in nodes) push(final_outbounds, n); +for (let ob in tpl.outbounds) push(final_outbounds, ob); + +/* expand __NODES__ placeholders */ +const node_tags = map(nodes, (n) => n.tag); +expand_node_placeholder(final_outbounds, node_tags); + +tpl.outbounds = final_outbounds; + +const out = sprintf("%.J\n", tpl); +if (out_path) { + if (!writefile(out_path, out)) + die(sprintf("writefile failed: %s", out_path)); + logerr(sprintf("wrote %s (%d bytes, %d nodes)", out_path, length(out), length(nodes))); +} else { + print(out); +} diff --git a/clashoo/files/usr/share/clashoo/migrate_fake_ip_filter.sh b/clashoo/files/usr/share/clashoo/migrate_fake_ip_filter.sh new file mode 100755 index 00000000..0cc4f362 --- /dev/null +++ b/clashoo/files/usr/share/clashoo/migrate_fake_ip_filter.sh @@ -0,0 +1,42 @@ +#!/bin/sh + +set -f + +[ "$(uci -q get clashoo.config.fake_ip_filter_migrated 2>/dev/null)" = "1" ] && exit 0 + +filters="$(uci -q get clashoo.config.fake_ip_filter 2>/dev/null)" +has_legacy=0 +for filter in $filters; do + [ "$filter" = "*.lan" ] && has_legacy=1 +done + +if [ "$has_legacy" = "1" ]; then + uci -q delete clashoo.config.fake_ip_filter + added_lan=0 + added_local=0 + for filter in $filters; do + case "$filter" in + '*.lan'|'+.lan') + if [ "$added_lan" = "0" ]; then + uci -q add_list clashoo.config.fake_ip_filter='+.lan' + added_lan=1 + fi + ;; + '+.local') + if [ "$added_local" = "0" ]; then + uci -q add_list clashoo.config.fake_ip_filter='+.local' + added_local=1 + fi + ;; + *) + uci -q add_list clashoo.config.fake_ip_filter="$filter" + ;; + esac + done + if [ "$added_local" = "0" ]; then + uci -q add_list clashoo.config.fake_ip_filter='+.local' + fi +fi + +uci -q set clashoo.config.fake_ip_filter_migrated='1' +uci -q commit clashoo diff --git a/clashoo/files/usr/share/clashoo/migrate_lan_acl.sh b/clashoo/files/usr/share/clashoo/migrate_lan_acl.sh new file mode 100644 index 00000000..750e3994 --- /dev/null +++ b/clashoo/files/usr/share/clashoo/migrate_lan_acl.sh @@ -0,0 +1,38 @@ +#!/bin/sh + +[ "$(uci -q get clashoo.config.acl_migrated 2>/dev/null)" = "1" ] && exit 0 +if uci -q show clashoo 2>/dev/null | grep -q '=lan_acl$'; then + uci -q set clashoo.config.acl_migrated='1' + uci -q commit clashoo + exit 0 +fi + +mode="$(uci -q get clashoo.config.access_control 2>/dev/null)" + +add_group() { + local dns="$1" proxy="$2" list_key="${3:-}" section value + + section="$(uci -q add clashoo lan_acl)" || exit 1 + uci -q set clashoo."$section".enabled='1' + uci -q set clashoo."$section".dns="$dns" + uci -q set clashoo."$section".proxy="$proxy" + if [ -n "$list_key" ]; then + for value in $(uci -q get clashoo.config."$list_key" 2>/dev/null); do + uci -q add_list clashoo."$section".ip="$value" + done + fi +} + +case "$mode" in + 1) + add_group 1 1 proxy_lan_ips + add_group 0 0 + ;; + 2) + add_group 0 0 reject_lan_ips + add_group 1 1 + ;; +esac + +uci -q set clashoo.config.acl_migrated='1' +uci -q commit clashoo diff --git a/clashoo/files/usr/share/clashoo/net/access_check.sh b/clashoo/files/usr/share/clashoo/net/access_check.sh new file mode 100755 index 00000000..6356d841 --- /dev/null +++ b/clashoo/files/usr/share/clashoo/net/access_check.sh @@ -0,0 +1,89 @@ +#!/bin/sh +# clashoo access check +# 用法: access_check.sh [mode] +# mode = direct | proxy(默认 proxy) +# direct: 经 openwrt 主干直连,不走任何代理 +# proxy: 走 clashoo 本地代理端口,验证核心出站 + +url="$1" +mode="${2:-proxy}" +[ -n "$url" ] || exit 1 + +attempts=1 +ok=0 +sum_ms=0 +last_code=000 + +url_host() { + printf '%s' "$1" | sed -n 's#^[a-zA-Z][a-zA-Z0-9+.-]*://\([^/:?#]*\).*$#\1#p' +} + +url_port() { + _u="$1" + _p="$(printf '%s' "$_u" | sed -n 's#^[a-zA-Z][a-zA-Z0-9+.-]*://[^/:?#]*:\([0-9][0-9]*\).*$#\1#p')" + [ -n "$_p" ] && { printf '%s' "$_p"; return; } + case "$_u" in + https://*) printf '443' ;; + http://*) printf '80' ;; + *) printf '443' ;; + esac +} + +run_with_timeout() { + _secs="$1" + shift + if command -v timeout >/dev/null 2>&1; then + timeout "$_secs" "$@" 2>/dev/null + else + "$@" 2>/dev/null + fi +} + +resolve_real_ip() { + _host="$1" + [ -n "$_host" ] || return 0 + for _dns in 223.5.5.5 119.29.29.29 1.1.1.1; do + _ip="$(run_with_timeout 1 nslookup "$_host" "$_dns" | awk '/^Address: /{print $2} /^Address [0-9]+: /{print $3}' | grep -E '^[0-9]+(\.[0-9]+){3}$' | tail -n1)" + [ -n "$_ip" ] && { printf '%s' "$_ip"; return; } + done +} + +set -- -4 -L --max-time 5 --connect-timeout 3 -s -o /dev/null -w '%{http_code} %{time_total}' +if [ "$mode" = "direct" ]; then + # 强制不走任何代理,避免误读 http_proxy 环境变量 + set -- "$@" --noproxy '*' + host="$(url_host "$url")" + port="$(url_port "$url")" + real_ip="$(resolve_real_ip "$host")" + [ -n "$host" ] && [ -n "$port" ] && [ -n "$real_ip" ] && set -- "$@" --resolve "${host}:${port}:${real_ip}" +else + proxy_port="$(uci get clashoo.config.mixed_port 2>/dev/null)" + [ -z "$proxy_port" ] && proxy_port="$(uci get clashoo.config.http_port 2>/dev/null)" + [ -z "$proxy_port" ] && proxy_port=7890 + set -- "$@" -x "http://127.0.0.1:${proxy_port}" +fi +set -- "$@" "$url" + +i=1 +while [ "$i" -le "$attempts" ]; do + out="$(curl "$@" 2>/dev/null || true)" + code="$(printf '%s' "$out" | awk '{print $1}')" + time_s="$(printf '%s' "$out" | awk '{print $2}')" + [ -n "$code" ] || code=000 + [ -n "$time_s" ] || time_s=0 + ms="$(awk -v t="$time_s" 'BEGIN{printf "%d", t*1000}')" + last_code="$code" + if [ "$code" = "200" ] || [ "$code" = "204" ] || [ "$code" = "301" ] || [ "$code" = "302" ]; then + ok=$((ok + 1)) + sum_ms=$((sum_ms + ms)) + fi + i=$((i + 1)) +done + +loss=$((attempts - ok)) +avg_ms=0 +if [ "$ok" -gt 0 ]; then + avg_ms=$((sum_ms / ok)) +fi + +echo "ok=$ok attempts=$attempts loss=$loss avg_ms=$avg_ms code=$last_code" diff --git a/clashoo/files/usr/share/clashoo/net/access_check_cache.sh b/clashoo/files/usr/share/clashoo/net/access_check_cache.sh new file mode 100755 index 00000000..ff930eb0 --- /dev/null +++ b/clashoo/files/usr/share/clashoo/net/access_check_cache.sh @@ -0,0 +1,195 @@ +#!/bin/sh + +set -eu + +CACHE_DIR="/tmp/clashoo" +CACHE_FILE="/tmp/clashoo_check_cache" +LOCK_DIR="${CACHE_DIR}/access_check.lock" +LOCK_PID_FILE="${LOCK_DIR}/pid" +UPDATING_FLAG="${CACHE_DIR}/access_check_updating" +TMP_FILE="${CACHE_FILE}.tmp.$$" +DIAG_LOG="/tmp/clashoo_access_check.log" + +take_lock() { + mkdir "$LOCK_DIR" 2>/dev/null || return 1 + printf '%s\n' "$$" > "$LOCK_PID_FILE" 2>/dev/null + return 0 +} + +pid_is_cache_worker() { + [ -r "/proc/$1/cmdline" ] || return 1 + tr '\0' ' ' <"/proc/$1/cmdline" 2>/dev/null | grep -q 'access_check_cache\.sh' +} + +lock_is_dead() { + _pid="$(cat "$LOCK_PID_FILE" 2>/dev/null)" + case "$_pid" in + ''|*[!0-9]*) + [ -n "$(find "$LOCK_DIR" -maxdepth 0 -mmin +3 2>/dev/null)" ] + return $? + ;; + esac + [ ! -d "/proc/$_pid" ] && return 0 + ! pid_is_cache_worker "$_pid" +} + +mkdir -p "$CACHE_DIR" +if ! take_lock; then + lock_is_dead || exit 0 + rm -rf "$LOCK_DIR" >/dev/null 2>&1 + take_lock || exit 0 +fi +trap 'rm -rf "$LOCK_DIR" "$UPDATING_FLAG" "$TMP_FILE"' EXIT INT TERM +: > "$UPDATING_FLAG" + +safe_int() { + case "${1:-}" in + ''|*[!0-9]*) + printf '0' + ;; + *) + printf '%s' "$1" + ;; + esac +} + +safe_code() { + case "${1:-}" in + ''|*[!0-9A-Za-z]*) + printf '000' + ;; + *) + printf '%s' "$1" + ;; + esac +} + +parse_field() { + _line="$1" + _key="$2" + printf '%s\n' "$_line" | sed -n "s/.*${_key}=\\([^ ]*\\).*/\\1/p" +} + +probe_json() { + _line="$1" + _ok="$(safe_int "$(parse_field "$_line" "ok")")" + _attempts="$(safe_int "$(parse_field "$_line" "attempts")")" + _loss="$(safe_int "$(parse_field "$_line" "loss")")" + _avg_ms="$(safe_int "$(parse_field "$_line" "avg_ms")")" + _code="$(safe_code "$(parse_field "$_line" "code")")" + + _state="down" + if [ "$_ok" -ge "$_attempts" ] && [ "$_attempts" -gt 0 ]; then + if [ "$_avg_ms" -ge 2500 ]; then + _state="high_latency" + else + _state="ok" + fi + elif [ "$_ok" -gt 0 ]; then + _state="loss" + fi + + _ok_bool=false + [ "$_ok" -gt 0 ] && _ok_bool=true + + printf '{"ok":%s,"state":"%s","code":"%s","ok_count":%s,"attempts":%s,"loss":%s,"avg_ms":%s}' \ + "$_ok_bool" "$_state" "$_code" "$_ok" "$_attempts" "$_loss" "$_avg_ms" +} + +log_diag() { + _line="$(date '+%Y-%m-%d %H:%M:%S') $1" + printf '%s\n' "$_line" >>"$DIAG_LOG" 2>/dev/null + _n="$(wc -l <"$DIAG_LOG" 2>/dev/null || echo 0)" + if [ "${_n:-0}" -gt 200 ]; then + tail -n 100 "$DIAG_LOG" >"${DIAG_LOG}.tmp" 2>/dev/null && mv "${DIAG_LOG}.tmp" "$DIAG_LOG" 2>/dev/null + fi +} + +probe_run() { + _url="$1" + _mode="$2" + # nice + ionice:探测 IO/CPU 都低优先级,避免抢占 LuCI dispatcher + nice -n 19 /usr/share/clashoo/net/access_check.sh "$_url" "$_mode" 2>/dev/null || true +} + +proxy_port="$(uci -q get clashoo.config.mixed_port)" +[ -z "$proxy_port" ] && proxy_port="$(uci -q get clashoo.config.http_port)" +[ -z "$proxy_port" ] && proxy_port="7890" +tcp_mode="$(uci -q get clashoo.config.tcp_mode)" +[ -z "$tcp_mode" ] && tcp_mode="redirect" +udp_mode="$(uci -q get clashoo.config.udp_mode)" +[ -z "$udp_mode" ] && udp_mode="$tcp_mode" + +proxy_listening() { + # 检测 mixed-port 是否在 LISTEN(clashoo 未运行时立即标 proxy down,避免显示假绿) + if command -v ss >/dev/null 2>&1; then + ss -tln 2>/dev/null | awk -v p=":${proxy_port}" '$0 ~ "LISTEN" && index($4, p) {found=1; exit} END{exit !found}' + elif command -v netstat >/dev/null 2>&1; then + netstat -tln 2>/dev/null | awk -v p=":${proxy_port}" '$0 ~ "LISTEN" && index($4, p) {found=1; exit} END{exit !found}' + else + return 0 + fi +} + +# 并行探测,把 CPU 抢占窗口从串行 2s+ 压缩到最慢一路的耗时 +f_db="${TMP_FILE}.db" +f_dy="${TMP_FILE}.dy" +f_pb="${TMP_FILE}.pb" +f_py="${TMP_FILE}.py" +probe_run "https://www.douyin.com/generate_204" "direct" >"$f_db" & +probe_run "https://www.youtube.com/generate_204" "direct" >"$f_dy" & +if proxy_listening; then + proxy_skipped=0 + probe_run "https://www.douyin.com/generate_204" "proxy" >"$f_pb" & + probe_run "https://www.youtube.com/generate_204" "proxy" >"$f_py" & +else + # 代理端口未监听(clashoo 已停止),跳过探测,直接标 down + proxy_skipped=1 + echo "ok=0 attempts=1 loss=1 avg_ms=0 code=skip" >"$f_pb" + echo "ok=0 attempts=1 loss=1 avg_ms=0 code=skip" >"$f_py" +fi +wait +direct_bytedance="$(cat "$f_db" 2>/dev/null)" +direct_youtube="$(cat "$f_dy" 2>/dev/null)" +proxy_bytedance="$(cat "$f_pb" 2>/dev/null)" +proxy_youtube="$(cat "$f_py" 2>/dev/null)" +rm -f "$f_db" "$f_dy" "$f_pb" "$f_py" + +updated_at="$(date +%s)" + +probe_all_down() { + for _p in "$direct_bytedance" "$direct_youtube" "$proxy_bytedance" "$proxy_youtube"; do + case "$_p" in + ''|*"ok=0 "*) ;; + *) return 1 ;; + esac + done + return 0 +} + +if [ "$(uci -q get clashoo.config.access_check_debug)" = "1" ] || probe_all_down; then + log_diag "port=${proxy_port} proxy_skipped=${proxy_skipped} direct_bd=[${direct_bytedance}] direct_yt=[${direct_youtube}] proxy_bd=[${proxy_bytedance}] proxy_yt=[${proxy_youtube}]" +fi + +cat >"$TMP_FILE" </dev/null; then + old_pid="$(cat "$PID_FILE" 2>/dev/null)" + if [ -n "$old_pid" ] && [ -d "/proc/$old_pid" ] && \ + tr '\0' ' ' <"/proc/$old_pid/cmdline" 2>/dev/null | grep -q 'access_check_daemon\.sh'; then + exit 0 + fi + rm -rf "$LOCK_DIR" "$PID_FILE" >/dev/null 2>&1 + mkdir "$LOCK_DIR" 2>/dev/null || exit 0 +fi + +cleanup() { + rm -rf "$LOCK_DIR" "$PID_FILE" +} +trap cleanup EXIT +trap 'cleanup; exit 0' INT TERM + +echo "$$" >"$PID_FILE" + +while :; do + [ "$(uci -q get clashoo.config.enable 2>/dev/null)" = "1" ] || break + /usr/share/clashoo/net/access_check_cache.sh >/dev/null 2>&1 || true + # 每轮顺手卡一下 core.log 大小(tmpfs 很小,跑飞会写满 /tmp) + sh /usr/share/clashoo/lib/core_log_guard.sh >/dev/null 2>&1 || true + if [ -n "$INTERVAL_FIXED" ]; then + sleep "$INTERVAL_FIXED" + else + sleep 30 + fi +done diff --git a/clashoo/files/usr/share/clashoo/net/dns_auto_setup.sh b/clashoo/files/usr/share/clashoo/net/dns_auto_setup.sh new file mode 100644 index 00000000..b1e22c8d --- /dev/null +++ b/clashoo/files/usr/share/clashoo/net/dns_auto_setup.sh @@ -0,0 +1,240 @@ +#!/bin/sh + +APPLY=0 +TIMEOUT="${CLASHOO_DNS_TEST_TIMEOUT:-2}" +START_TS="$(date +%s 2>/dev/null || echo 0)" + +while [ $# -gt 0 ]; do + case "$1" in + --apply) APPLY=1 ;; + esac + shift +done + +json_escape() { + printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g' +} + +json_pair() { + printf '"%s":"%s"' "$1" "$(json_escape "$2")" +} + +is_ipv4() { + echo "$1" | grep -Eq '^[0-9]+(\.[0-9]+){3}$' +} + +append_unique() { + list="$1" + item="$2" + [ -n "$item" ] || { printf '%s' "$list"; return; } + case " $list " in + *" $item "*) printf '%s' "$list" ;; + *) printf '%s %s' "$list" "$item" ;; + esac +} + +current_default_nameservers() { + uci -q get clashoo.config.default_nameserver 2>/dev/null || true +} + +current_dns_servers() { + uci -q show clashoo 2>/dev/null | awk -F= ' + function clean(v) { + gsub(/^'\''|'\''$/, "", v); + return v; + } + /^clashoo\.@dnsservers\[[0-9]+\]\./ { + line=$1; + val=clean($2); + sub(/^clashoo\.@dnsservers\[/, "", line); + idx=line; + sub(/\].*$/, "", idx); + opt=line; + sub(/^[0-9]+\]\./, "", opt); + data[idx, opt]=val; + seen[idx]=1; + } + END { + for (idx in seen) { + if (data[idx, "enabled"] == "0") continue; + role=data[idx, "ser_type"]; + addr=data[idx, "ser_address"]; + proto=data[idx, "protocol"]; + port=data[idx, "ser_port"]; + if (addr == "") continue; + if (proto != "" && proto != "none" && addr !~ /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//) { + addr=proto addr; + if (port != "" && addr !~ /:[0-9]+$/) addr=addr ":" port; + } + print role "|" addr; + } + }' +} + +servers_for_role() { + role="$1" + current_dns_servers | awk -F'|' -v role="$role" '$1 == role { print $2 }' +} + +candidate_host_port() { + uri="$1" + default_port="$2" + hostport="${uri#*://}" + hostport="${hostport%%/*}" + host="${hostport%%:*}" + port="${hostport##*:}" + [ "$port" != "$hostport" ] || port="$default_port" + printf '%s %s\n' "$host" "$port" +} + +probe_tls() { + uri="$1" + set -- $(candidate_host_port "$uri" 853) + host="$1" + port="$2" + command -v nc >/dev/null 2>&1 || return 1 + nc -w "$TIMEOUT" "$host" "$port" >/dev/null 2>&1 +} + +probe_ip_dns() { + ip="$1" + domain="$2" + command -v nslookup >/dev/null 2>&1 || return 1 + nslookup "$domain" "$ip" >/dev/null 2>&1 +} + +probe_latency_ms() { + candidate="$1" + domain="$2" + if [ "${candidate#https://}" != "$candidate" ]; then + if printf '%s' "$candidate" | grep -q '?'; then + q="$candidate&name=$domain&type=A" + else + q="$candidate?name=$domain&type=A" + fi + command -v curl >/dev/null 2>&1 || return 1 + t="$(curl -fsS --connect-timeout "$TIMEOUT" --max-time "$TIMEOUT" \ + -H 'accept: application/dns-json' -o /dev/null -w '%{time_total}' "$q" 2>/dev/null)" || return 1 + printf '%s' "$t" | awk '{ms=$1*1000; if (ms<1) ms=1; printf "%d", ms}' + elif [ "${candidate#tls://}" != "$candidate" ]; then + probe_tls "$candidate" || return 1 + printf '900' + elif [ "${candidate#udp://}" != "$candidate" ]; then + probe_ip_dns "${candidate#udp://}" "$domain" || return 1 + printf '950' + elif [ "${candidate#tcp://}" != "$candidate" ]; then + probe_ip_dns "${candidate#tcp://}" "$domain" || return 1 + printf '950' + else + is_ipv4 "$candidate" && probe_ip_dns "$candidate" "$domain" || return 1 + printf '950' + fi +} + +select_best() { + list="$1" + domain="$2" + fallback="$3" + best="" + best_score=999999 + order=0 + for candidate in $list; do + order=$((order + 1)) + ms="$(probe_latency_ms "$candidate" "$domain")" || { FAILED_COUNT=$((FAILED_COUNT + 1)); continue; } + score=$((ms * 100 + order)) + if [ "$score" -lt "$best_score" ]; then + best="$candidate" + best_score="$score" + fi + done + [ -n "$best" ] || best="$fallback" + printf '%s' "$best" +} + +add_dnsserver() { + role="$1" + addr="$2" + proto="$3" + port="$4" + sec="$(uci -q add clashoo dnsservers)" || return 1 + [ -n "$sec" ] || return 1 + uci -q set clashoo."$sec".enabled='1' || return 1 + uci -q set clashoo."$sec".ser_type="$role" || return 1 + uci -q set clashoo."$sec".ser_address="$addr" || return 1 + uci -q set clashoo."$sec".protocol="$proto" || return 1 + if [ -n "$port" ]; then + uci -q set clashoo."$sec".ser_port="$port" || return 1 + fi + return 0 +} + +set_default_nameserver() { + uci -q delete clashoo.config.default_nameserver >/dev/null 2>&1 || true + uci -q add_list clashoo.config.default_nameserver="$1" || return 1 + return 0 +} + +apply_result() { + uci -q set clashoo.config.enable_dns='1' || { uci -q revert clashoo; return 1; } + set_default_nameserver "$BOOTSTRAP" || { uci -q revert clashoo; return 1; } + while uci -q delete clashoo.@dnsservers[0] >/dev/null 2>&1; do :; done + add_dnsserver 'nameserver' "$NAMESERVER" 'none' '' || { uci -q revert clashoo; return 1; } + add_dnsserver 'direct-nameserver' "$BOOTSTRAP" 'udp://' '' || { uci -q revert clashoo; return 1; } + add_dnsserver 'proxy-server-nameserver' "$PROXY_NS" 'none' '' || { uci -q revert clashoo; return 1; } + add_dnsserver 'fallback' "$FALLBACK_NS" 'none' '' || { uci -q revert clashoo; return 1; } + uci -q commit clashoo || { uci -q revert clashoo; return 1; } + return 0 +} + +is_domestic_dns() { + printf '%s' "$1" | grep -qE 'alidns|doh\.pub|dnspod|223\.5\.5\.5|223\.6\.6\.6|119\.29\.29\.29|119\.28\.28\.28|114\.114|180\.184|onedns' +} +is_overseas_dns() { + printf '%s' "$1" | grep -qE 'cloudflare|1\.1\.1\.1|1\.0\.0\.1|one\.one\.one\.one|dns\.google|8\.8\.8\.8|8\.8\.4\.4|9\.9\.9\.9|quad9|opendns|208\.67\.' +} + +FAILED_COUNT=0 +BOOTSTRAP_CANDIDATES="223.5.5.5 119.29.29.29 180.184.1.1" +DOMESTIC_CANDIDATES="https://dns.alidns.com/dns-query https://doh.pub/dns-query udp://223.5.5.5 udp://119.29.29.29" +FALLBACK_CANDIDATES="https://cloudflare-dns.com/dns-query https://dns.google/dns-query tls://1.1.1.1:853" + +for ns in $(current_default_nameservers); do + is_ipv4 "$ns" && BOOTSTRAP_CANDIDATES="$(append_unique "$BOOTSTRAP_CANDIDATES" "$ns")" +done +for ns in $(servers_for_role 'nameserver') $(servers_for_role 'direct-nameserver'); do + is_domestic_dns "$ns" && DOMESTIC_CANDIDATES="$(append_unique "$DOMESTIC_CANDIDATES" "$ns")" +done +for ns in $(servers_for_role 'fallback'); do + is_overseas_dns "$ns" && FALLBACK_CANDIDATES="$(append_unique "$FALLBACK_CANDIDATES" "$ns")" +done + +BOOTSTRAP="$(select_best "$BOOTSTRAP_CANDIDATES" www.baidu.com 223.5.5.5)" +NAMESERVER="$(select_best "$DOMESTIC_CANDIDATES" www.baidu.com https://dns.alidns.com/dns-query)" +PROXY_NS="tls://1.1.1.1:853" +FALLBACK_NS="$(select_best "$FALLBACK_CANDIDATES" www.google.com https://cloudflare-dns.com/dns-query)" + +APPLIED=false +if [ "$APPLY" = "1" ]; then + if apply_result; then + APPLIED=true + else + printf '{"success":false,"error":"apply_failed","message":"DNS 自动配置写入失败"}\n' + exit 1 + fi +fi + +END_TS="$(date +%s 2>/dev/null || echo "$START_TS")" +ELAPSED_MS=$(( (END_TS - START_TS) * 1000 )) + +printf '{' +printf '"success":true,' +printf '"applied":%s,' "$APPLIED" +printf '"restarted":false,' +json_pair bootstrap "$BOOTSTRAP"; printf ',' +json_pair nameserver "$NAMESERVER"; printf ',' +json_pair direct_nameserver "$BOOTSTRAP"; printf ',' +json_pair proxy_nameserver "$PROXY_NS"; printf ',' +json_pair fallback "$FALLBACK_NS"; printf ',' +printf '"failed_count":%s,' "$FAILED_COUNT" +printf '"elapsed_ms":%s' "$ELAPSED_MS" +printf '}\n' diff --git a/clashoo/files/usr/share/clashoo/net/fw4.sh b/clashoo/files/usr/share/clashoo/net/fw4.sh new file mode 100755 index 00000000..540c6b05 --- /dev/null +++ b/clashoo/files/usr/share/clashoo/net/fw4.sh @@ -0,0 +1,898 @@ +#!/bin/sh + +set -eu + +NFT_DIR="/var/run/clash" +SETS_RULES="${NFT_DIR}/fw4_sets.nft" +DSTNAT_RULES="${NFT_DIR}/fw4_dstnat.nft" +MANGLE_RULES="${NFT_DIR}/fw4_mangle.nft" +OUTPUT_RULES="${NFT_DIR}/fw4_output.nft" +BUILTIN_NFT_DIR="/usr/share/clashoo/nftables" +GEOIP_CN_NFT="${BUILTIN_NFT_DIR}/geoip_cn.nft" +GEOIP6_CN_NFT="${BUILTIN_NFT_DIR}/geoip6_cn.nft" +LOCAL_OUTPUT_TABLE="clashoo_local" +QUIC_BLOCK_TABLE="clashoo_quic" +# PROXY_FWMARK: inbound TPROXY mark, ip rule -> table PROXY_ROUTE_TABLE -> lo. +# CORE_ROUTING_MARK: mihomo outbound SO_MARK (= routing-mark in config). +# Must differ: otherwise mihomo egress is pulled to lo -> unreachable. +# When changing CORE_ROUTING_MARK, also update yum_change.sh routing_mark_dec. +PROXY_FWMARK="0x162" +PROXY_ROUTE_TABLE="0x162" +CORE_ROUTING_MARK="0x1a0a" # = 6666 +ACL_BYPASS_FWMARK="0x163" +SINGBOX_BYPASS_FWMARK="0x2024" +ACL_BYPASS_PREF="8998" +DNSMASQ_BYPASS_PREF="8999" + +uci_get() { + uci -q get "$1" 2>/dev/null || true +} + +bool_enabled() { + case "$1" in + 1|true|TRUE|yes|on) return 0 ;; + *) return 1 ;; + esac +} + +acl_bool() { + [ -n "$1" ] || return 0 + bool_enabled "$1" +} + +tun_available() { + ip tuntap add mode tun name cotuntest >/dev/null 2>&1 || return 1 + ip link del cotuntest >/dev/null 2>&1 || true + return 0 +} + +config_redir_port() { + uci_get clashoo.config.redir_port +} + +config_tproxy_port() { + local port + port="$(uci_get clashoo.config.tproxy_port)" + if [ -n "$port" ]; then + printf '%s\n' "$port" + else + config_redir_port + fi +} + +config_tcp_mode() { + uci_get clashoo.config.tcp_mode +} + +config_udp_mode() { + uci_get clashoo.config.udp_mode +} + +config_access_control() { + [ "$(uci_get clashoo.config.acl_migrated)" = "1" ] && { + printf '0\n' + return + } + uci_get clashoo.config.access_control +} + +acl_sections() { + uci -q show clashoo 2>/dev/null | sed -n 's/^clashoo\.\([^.=]*\)=lan_acl$/\1/p' +} + +acl_list() { + uci_get "$1" +} + +acl_sections_catchall_last() { + local section + for section in $(acl_sections); do + [ -z "$(acl_list "clashoo.${section}.ip")$(acl_list "clashoo.${section}.ip6")$(acl_list "clashoo.${section}.mac")" ] || printf '%s\n' "$section" + done + for section in $(acl_sections); do + [ -n "$(acl_list "clashoo.${section}.ip")$(acl_list "clashoo.${section}.ip6")$(acl_list "clashoo.${section}.mac")" ] || printf '%s\n' "$section" + done +} + +grouped_acl_enabled() { + local section + [ "$(uci_get clashoo.config.acl_migrated)" = "1" ] || return 1 + for section in $(acl_sections); do + [ "$(uci_get "clashoo.${section}.enabled")" != "0" ] && return 0 + done + return 1 +} + +acl_has_catchall() { + local section + for section in $(acl_sections); do + [ "$(uci_get "clashoo.${section}.enabled")" != "0" ] || continue + [ -z "$(acl_list "clashoo.${section}.ip")$(acl_list "clashoo.${section}.ip6")$(acl_list "clashoo.${section}.mac")" ] && return 0 + done + return 1 +} + +lan_acl_enabled() { + grouped_acl_enabled && return 0 + case "$(config_access_control)" in + 1|2) return 0 ;; + esac + return 1 +} + +config_enable_dns() { + uci_get clashoo.config.enable_dns +} + +config_dns_port() { + local port="" + if [ "$(uci_get clashoo.config.core_type)" != "singbox" ]; then + port="$(sed -n 's/^[[:space:]]*listen:.*:[[:space:]]*['"'"'"]*\([0-9]\{1,\}\)['"'"'"]*[[:space:]]*$/\1/p' /etc/clashoo/config.yaml 2>/dev/null | head -n1)" + fi + [ -n "$port" ] || port="$(uci_get clashoo.config.listen_port)" + printf '%s' "$port" | grep -Eq '^[0-9]+$' || port=1053 + printf '%s\n' "$port" +} + +lan_dns_split_enabled() { + lan_acl_enabled || return 1 + bool_enabled "$(config_enable_dns)" || return 1 + bool_enabled "$(config_ipv4_dns_hijack)" || bool_enabled "$(config_ipv6_dns_hijack)" +} + +tun_acl_enabled() { + lan_acl_enabled || return 1 + [ "$(config_tcp_mode)" = "tun" ] || [ "$(config_udp_mode)" = "tun" ] +} + +config_ipv4_dns_hijack() { + uci_get clashoo.config.ipv4_dns_hijack +} + +config_ipv6_dns_hijack() { + uci_get clashoo.config.ipv6_dns_hijack +} + +config_ipv4_proxy() { + uci_get clashoo.config.ipv4_proxy +} + +config_ipv6_proxy() { + uci_get clashoo.config.ipv6_proxy +} + +singbox_tun_active() { + [ "$(uci_get clashoo.config.core_type)" = "singbox" ] || return 1 + [ "$(config_tcp_mode)" = "tun" ] || [ "$(config_udp_mode)" = "tun" ] +} + +config_bypass_china() { + uci_get clashoo.config.bypass_china +} + +config_bypass_china_ipv6() { + local value + value="$(uci_get clashoo.config.bypass_china_ipv6)" + if [ -n "$value" ]; then + printf '%s\n' "$value" + else + config_bypass_china + fi +} + +config_block_quic() { + uci_get clashoo.config.block_quic +} + +config_bypass_port_mode() { + uci_get clashoo.config.bypass_port_mode +} + +config_bypass_port_custom() { + uci_get clashoo.config.bypass_port_custom +} + +config_legacy_bypass_port() { + uci_list clashoo.config.bypass_port +} + +config_proxy_tcp_dport() { + local mode custom legacy value + mode="$(config_bypass_port_mode)" + custom="$(config_bypass_port_custom)" + legacy="$(uci_get clashoo.config.proxy_tcp_dport)" + [ -z "$legacy" ] && legacy="$(config_legacy_bypass_port)" + + case "$mode" in + all) + # 空值在 render_port_match 中表示该协议的全部端口 + printf '%s\n' '' + ;; + common) + printf '%s\n' '22,53,80,443,8080,8443' + ;; + custom) + value="$custom" + [ -z "$value" ] && value="$legacy" + printf '%s\n' "$value" + ;; + *) + printf '%s\n' "$legacy" + ;; + esac +} + +config_proxy_udp_dport() { + local mode custom legacy value + mode="$(config_bypass_port_mode)" + custom="$(config_bypass_port_custom)" + legacy="$(uci_get clashoo.config.proxy_udp_dport)" + [ -z "$legacy" ] && legacy="$(config_legacy_bypass_port)" + + case "$mode" in + all) + printf '%s\n' '' + ;; + common) + printf '%s\n' '22,53,80,443,8080,8443' + ;; + custom) + value="$custom" + [ -z "$value" ] && value="$legacy" + printf '%s\n' "$value" + ;; + *) + printf '%s\n' "$legacy" + ;; + esac +} + +config_bypass_dscp() { + uci_list clashoo.config.bypass_dscp +} + +config_bypass_fwmark() { + uci_list clashoo.config.bypass_fwmark +} + +config_fake_ip_range6() { + local value + value="$(uci_get clashoo.config.fake_ip_range6)" + [ -n "$value" ] || value="fc00::/18" + [ "$(uci_get clashoo.config.enhanced_mode)" = "redir-host" ] && return 0 + bool_enabled "$(uci_get clashoo.config.enable_ipv6)" || return 0 + printf '%s\n' "$value" +} + +config_fake_ip_range() { + local value + value="$(uci_get clashoo.config.fake_ip_range)" + [ -n "$value" ] && { + printf '%s\n' "$value" + return + } + printf '198.18.0.1/16\n' +} + +uci_list() { + local key="$1" + uci -q show "$key" 2>/dev/null | sed -n "s/^${key}=//p" | sed "s/'//g" +} + +ensure_firewall_include() { + local name="$1" + local path="$2" + local chain="${3:-}" + local position="${4:-chain-pre}" + + uci -q batch <<-EOF >/dev/null + set firewall.${name}=include + set firewall.${name}.type='nftables' + set firewall.${name}.path='${path}' + set firewall.${name}.position='${position}' + $( [ -n "$chain" ] && printf "set firewall.%s.chain='%s'\n" "$name" "$chain" ) + commit firewall +EOF +} + +remove_firewall_include() { + local name="$1" + uci -q delete firewall."${name}" >/dev/null 2>&1 || true +} + +render_common_returns() { + local fake6 + fake6="$(config_fake_ip_range6)" + printf '%s\n' 'meta nfproto ipv4 ip daddr { 0.0.0.0/8, 10.0.0.0/8, 100.64.0.0/10, 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 } return' + if [ -n "$fake6" ]; then + printf 'meta nfproto ipv6 ip6 daddr { ::1/128, fc00::/7, fe80::/10, ff00::/8 } ip6 daddr != %s return\n' "$fake6" + else + printf '%s\n' 'meta nfproto ipv6 ip6 daddr { ::1/128, fc00::/7, fe80::/10, ff00::/8 } return' + fi +} + +render_ip_elements() { + local list="$1" + local first=1 entry + for entry in $list; do + if [ "$first" -eq 0 ]; then + printf ', ' + fi + printf '%s' "$entry" + first=0 + done +} + +render_token_elements() { + printf '%s\n' "$1" | tr ',\t' ' ' | awk ' + BEGIN { first = 1 } + { + for (i = 1; i <= NF; i++) { + if ($i == "") + continue + if (!first) + printf ", " + printf "%s", $i + first = 0 + } + }' +} + +detect_coexist_fwmarks() { + # Merge co-installed proxy plugin fwmarks into bypass to avoid intercepting + # their traffic. Only when init.d exists (passwall:0x1 passwall2:0xff + # nikki:tproxy 0x80/tun 0x81 mask 0xff). + local marks="" + [ -x /etc/init.d/passwall ] && marks="$marks 0x1" + [ -x /etc/init.d/passwall2 ] && marks="$marks 0xff" + if [ -x /etc/init.d/nikki ]; then + local nm + nm="$(uci -q get nikki.routing.tproxy_fw_mark) $(uci -q get nikki.routing.tun_fw_mark)" + [ -z "$(echo "$nm" | tr -d ' ')" ] && nm="0x80 0x81" + marks="$marks $nm" + fi + printf '%s\n' "$marks" +} + +merge_fwmark_tokens() { + # Always bypass PROXY_FWMARK (inbound TPROXY) and CORE_ROUTING_MARK (core egress) + # so nft never traps them. Also merge co-installed plugin fwmarks for coexistence. + local coexist + coexist="$(detect_coexist_fwmarks)" + printf '%s %s %s %s\n' "$1" "$PROXY_FWMARK" "$CORE_ROUTING_MARK" "$coexist" | tr ',\t' ' ' | awk ' + BEGIN { first = 1 } + { + for (i = 1; i <= NF; i++) { + if ($i == "" || seen[$i]++) + continue + if (!first) + printf ", " + printf "%s", $i + first = 0 + } + }' +} + +render_port_match() { + local proto="$1" + local ports="$2" + local port_elements + + port_elements="$(render_token_elements "$ports")" + if [ -n "$port_elements" ]; then + printf 'meta l4proto %s %s dport { %s }' "$proto" "$proto" "$port_elements" + else + printf 'meta l4proto %s' "$proto" + fi +} + +render_acl_dns_rules() { + local redirect_port="$1" section ipv4 ipv6 mac ipv4_elements ipv6_elements mac_elements action + for section in $(acl_sections_catchall_last); do + [ "$(uci_get "clashoo.${section}.enabled")" != "0" ] || continue + if acl_bool "$(uci_get "clashoo.${section}.dns")"; then + action="counter redirect to :${redirect_port}" + elif singbox_tun_active; then + action="meta mark set ${SINGBOX_BYPASS_FWMARK} ct mark set meta mark counter return" + else + action="counter return" + fi + ipv4="$(acl_list "clashoo.${section}.ip")" + ipv6="$(acl_list "clashoo.${section}.ip6")" + mac="$(acl_list "clashoo.${section}.mac")" + ipv4_elements="$(render_ip_elements "$ipv4")" + ipv6_elements="$(render_ip_elements "$ipv6")" + mac_elements="$(render_ip_elements "$mac")" + + if [ -z "$ipv4_elements$ipv6_elements$mac_elements" ]; then + bool_enabled "$(config_ipv4_dns_hijack)" && printf 'meta nfproto ipv4 meta l4proto { tcp, udp } th dport 53 %s\n' "$action" + bool_enabled "$(config_ipv6_dns_hijack)" && printf 'meta nfproto ipv6 meta l4proto { tcp, udp } th dport 53 %s\n' "$action" + continue + fi + if [ -n "$ipv4_elements" ] && bool_enabled "$(config_ipv4_dns_hijack)"; then + printf 'meta nfproto ipv4 ip saddr { %s } meta l4proto { tcp, udp } th dport 53 %s\n' "$ipv4_elements" "$action" + fi + if [ -n "$ipv6_elements" ] && bool_enabled "$(config_ipv6_dns_hijack)"; then + printf 'meta nfproto ipv6 ip6 saddr { %s } meta l4proto { tcp, udp } th dport 53 %s\n' "$ipv6_elements" "$action" + fi + if [ -n "$mac_elements" ]; then + bool_enabled "$(config_ipv4_dns_hijack)" && printf 'meta nfproto ipv4 ether saddr { %s } meta l4proto { tcp, udp } th dport 53 %s\n' "$mac_elements" "$action" + bool_enabled "$(config_ipv6_dns_hijack)" && printf 'meta nfproto ipv6 ether saddr { %s } meta l4proto { tcp, udp } th dport 53 %s\n' "$mac_elements" "$action" + fi + done +} + +render_acl_proxy_rules() { + local mode="$1" proto="$2" port_match="$3" target_port="$4" + local section ipv4 ipv6 mac ipv4_elements ipv6_elements mac_elements enabled action4 action6 + + for section in $(acl_sections_catchall_last); do + [ "$(uci_get "clashoo.${section}.enabled")" != "0" ] || continue + enabled="$(uci_get "clashoo.${section}.proxy")" + case "$mode" in + redirect) + if acl_bool "$enabled"; then + if singbox_tun_active; then + action4="${port_match} ct mark set ${SINGBOX_BYPASS_FWMARK} counter redirect to :${target_port}" + else + action4="${port_match} counter redirect to :${target_port}" + fi + action6="$action4" + elif singbox_tun_active; then + action4="meta l4proto ${proto} meta mark set ${SINGBOX_BYPASS_FWMARK} ct mark set meta mark counter return" + action6="$action4" + else + action4="meta l4proto ${proto} counter return" + action6="$action4" + fi + ;; + tproxy) + if acl_bool "$enabled"; then + if singbox_tun_active; then + action4="${port_match} ct mark set ${SINGBOX_BYPASS_FWMARK} tproxy ip to :${target_port} meta mark set ${PROXY_FWMARK} counter accept" + action6="${port_match} ct mark set ${SINGBOX_BYPASS_FWMARK} tproxy ip6 to :${target_port} meta mark set ${PROXY_FWMARK} counter accept" + else + action4="${port_match} tproxy ip to :${target_port} meta mark set ${PROXY_FWMARK} counter accept" + action6="${port_match} tproxy ip6 to :${target_port} meta mark set ${PROXY_FWMARK} counter accept" + fi + elif singbox_tun_active; then + action4="meta l4proto ${proto} meta mark set ${SINGBOX_BYPASS_FWMARK} ct mark set meta mark counter return" + action6="$action4" + else + action4="meta l4proto ${proto} counter return" + action6="$action4" + fi + ;; + tun) + if acl_bool "$enabled"; then + action4="meta l4proto ${proto} counter return" + elif singbox_tun_active; then + action4="meta l4proto ${proto} meta mark set ${SINGBOX_BYPASS_FWMARK} ct mark set meta mark counter accept" + else + action4="meta l4proto ${proto} meta mark set ${ACL_BYPASS_FWMARK} counter accept" + fi + action6="$action4" + ;; + esac + + ipv4="$(acl_list "clashoo.${section}.ip")" + ipv6="$(acl_list "clashoo.${section}.ip6")" + mac="$(acl_list "clashoo.${section}.mac")" + ipv4_elements="$(render_ip_elements "$ipv4")" + ipv6_elements="$(render_ip_elements "$ipv6")" + mac_elements="$(render_ip_elements "$mac")" + + if [ -z "$ipv4_elements$ipv6_elements$mac_elements" ]; then + bool_enabled "$(config_ipv4_proxy)" && printf 'meta nfproto ipv4 %s\n' "$action4" + bool_enabled "$(config_ipv6_proxy)" && printf 'meta nfproto ipv6 %s\n' "$action6" + continue + fi + if [ -n "$ipv4_elements" ] && bool_enabled "$(config_ipv4_proxy)"; then + printf 'meta nfproto ipv4 ip saddr { %s } %s\n' "$ipv4_elements" "$action4" + fi + if [ -n "$ipv6_elements" ] && bool_enabled "$(config_ipv6_proxy)"; then + printf 'meta nfproto ipv6 ip6 saddr { %s } %s\n' "$ipv6_elements" "$action6" + fi + if [ -n "$mac_elements" ]; then + bool_enabled "$(config_ipv4_proxy)" && printf 'meta nfproto ipv4 ether saddr { %s } %s\n' "$mac_elements" "$action4" + bool_enabled "$(config_ipv6_proxy)" && printf 'meta nfproto ipv6 ether saddr { %s } %s\n' "$mac_elements" "$action6" + fi + done +} + +# Split proxied clients to the core DNS while ACL-bypassed clients use dnsmasq. +# Gated on UCI IPv4/IPv6 toggles. +# return 0: no output when both off, avoid set -e false-positive. +render_dns_hijack() { + local access_control="$1" redirect_port=53 + if lan_dns_split_enabled; then + redirect_port="$(config_dns_port)" + fi + if grouped_acl_enabled; then + render_acl_dns_rules "$redirect_port" + acl_has_catchall && return 0 + else + [ "$access_control" = "1" ] && printf '%s\n' 'ip saddr != @clash_proxy_lan return' + [ "$access_control" = "2" ] && printf '%s\n' 'ip saddr @clash_reject_lan return' + fi + bool_enabled "$(config_ipv4_dns_hijack)" && \ + printf '%s\n' "meta nfproto ipv4 meta l4proto { tcp, udp } th dport 53 counter redirect to :${redirect_port}" + bool_enabled "$(config_ipv6_dns_hijack)" && \ + printf '%s\n' "meta nfproto ipv6 meta l4proto { tcp, udp } th dport 53 counter redirect to :${redirect_port}" + return 0 +} + +apply_local_output_rule() { + local redir_port fake_ip_range tcp_mode bypass_fwmark bypass_china + local fwmark_elements fwmark_rule china_set china_rule dnsmasq_rule dnsmasq_uid + redir_port="$(config_redir_port)" + fake_ip_range="$(config_fake_ip_range)" + tcp_mode="$(config_tcp_mode)" + bypass_fwmark="$(config_bypass_fwmark)" + bypass_china="$(config_bypass_china)" + + # Keep local-output redirect usable when tun mode is selected but tun + # device is unavailable on the system. + if [ "$tcp_mode" = "tun" ] && ! tun_available; then + tcp_mode="redirect" + fi + + nft delete table ip ${LOCAL_OUTPUT_TABLE} >/dev/null 2>&1 || true + + # Only apply local output redirect when tcp_mode is redirect + [ "$tcp_mode" != "redirect" ] && return 0 + + # bypass mihomo's own marks (prevent self-hijack -> dead loop) + fwmark_rule="" + fwmark_elements="$(merge_fwmark_tokens "$bypass_fwmark")" + [ -n "$fwmark_elements" ] && fwmark_rule="meta mark { ${fwmark_elements} } return" + + # CN IP bypass (uses clashoo_china set from geoip_cn.nft) + china_set="" + china_rule="" + if bool_enabled "$bypass_china" && [ -s "$GEOIP_CN_NFT" ]; then + china_set="$(cat "$GEOIP_CN_NFT")" + china_rule="ip daddr @clashoo_china return" + fi + + dnsmasq_rule="" + if lan_dns_split_enabled; then + dnsmasq_uid="$(id -u dnsmasq 2>/dev/null)" + printf '%s' "$dnsmasq_uid" | grep -Eq '^[0-9]+$' && dnsmasq_rule="meta skuid ${dnsmasq_uid} return" + fi + + nft -f - </dev/null 2>&1 || true +} + +remove_acl_bypass_rules() { + local dnsmasq_uid + ip rule del pref "$ACL_BYPASS_PREF" fwmark "$ACL_BYPASS_FWMARK" lookup main >/dev/null 2>&1 || true + ip -6 rule del pref "$ACL_BYPASS_PREF" fwmark "$ACL_BYPASS_FWMARK" lookup main >/dev/null 2>&1 || true + dnsmasq_uid="$(id -u dnsmasq 2>/dev/null)" + if printf '%s' "$dnsmasq_uid" | grep -Eq '^[0-9]+$'; then + ip rule del pref "$DNSMASQ_BYPASS_PREF" uidrange "${dnsmasq_uid}-${dnsmasq_uid}" lookup main >/dev/null 2>&1 || true + ip -6 rule del pref "$DNSMASQ_BYPASS_PREF" uidrange "${dnsmasq_uid}-${dnsmasq_uid}" lookup main >/dev/null 2>&1 || true + fi +} + +apply_acl_bypass_rules() { + local dnsmasq_uid + remove_acl_bypass_rules + tun_acl_enabled || return 0 + ip rule add pref "$ACL_BYPASS_PREF" fwmark "$ACL_BYPASS_FWMARK" lookup main >/dev/null 2>&1 || true + ip -6 rule add pref "$ACL_BYPASS_PREF" fwmark "$ACL_BYPASS_FWMARK" lookup main >/dev/null 2>&1 || true + dnsmasq_uid="$(id -u dnsmasq 2>/dev/null)" + if printf '%s' "$dnsmasq_uid" | grep -Eq '^[0-9]+$'; then + ip rule add pref "$DNSMASQ_BYPASS_PREF" uidrange "${dnsmasq_uid}-${dnsmasq_uid}" lookup main >/dev/null 2>&1 || true + ip -6 rule add pref "$DNSMASQ_BYPASS_PREF" uidrange "${dnsmasq_uid}-${dnsmasq_uid}" lookup main >/dev/null 2>&1 || true + fi +} + +apply_block_quic_rule() { + local fake_ip_range china_set china_rule scope + nft delete table inet ${QUIC_BLOCK_TABLE} >/dev/null 2>&1 || true + bool_enabled "$(config_block_quic)" || return 0 + + fake_ip_range="$(config_fake_ip_range)" + china_set="" + scope="ip daddr ${fake_ip_range}" + if [ "$(uci_get clashoo.config.enhanced_mode)" != "fake-ip" ]; then + if bool_enabled "$(config_bypass_china)" && [ -s "$GEOIP_CN_NFT" ]; then + china_set="$(cat "$GEOIP_CN_NFT")" + china_rule="ip daddr @clashoo_china return" + fi + scope="" + fi + + nft -f - </dev/null 2>&1 || true +} + +write_empty_set() { + local set_name="$1" + local set_type="$2" + + printf 'set %s {\n\ttype %s;\n\tflags interval;\n\tauto-merge;\n}\n\n' "$set_name" "$set_type" +} + +append_set_from_file_or_empty() { + local file_path="$1" + local set_name="$2" + local set_type="$3" + + if [ -s "$file_path" ]; then + cat "$file_path" + printf '\n' + else + write_empty_set "$set_name" "$set_type" + fi +} + +generate_rules() { + local redir_port tproxy_port tcp_mode udp_mode access_control fake_ip_range proxy_lan_ips reject_lan_ips + local proxy_tcp_dport proxy_udp_dport bypass_dscp bypass_fwmark bypass_china bypass_china_ipv6 + local acl_catchall=0 + redir_port="$(config_redir_port)" + tproxy_port="$(config_tproxy_port)" + tcp_mode="$(config_tcp_mode)" + udp_mode="$(config_udp_mode)" + access_control="$(config_access_control)" + grouped_acl_enabled && acl_has_catchall && acl_catchall=1 + bypass_china="$(config_bypass_china)" + bypass_china_ipv6="$(config_bypass_china_ipv6)" + proxy_tcp_dport="$(config_proxy_tcp_dport)" + proxy_udp_dport="$(config_proxy_udp_dport)" + bypass_dscp="$(config_bypass_dscp)" + bypass_fwmark="$(config_bypass_fwmark)" + fake_ip_range="$(config_fake_ip_range)" + proxy_lan_ips="$(uci_list clashoo.config.proxy_lan_ips)" + reject_lan_ips="$(uci_list clashoo.config.reject_lan_ips)" + + # When tun device is unavailable, fall back to non-tun transparent modes + # so routing rules still take effect for sing-box redirect/tproxy inbounds. + if [ "$tcp_mode" = "tun" ] || [ "$udp_mode" = "tun" ]; then + if ! tun_available; then + [ "$tcp_mode" = "tun" ] && tcp_mode="redirect" + [ "$udp_mode" = "tun" ] && udp_mode="tproxy" + fi + fi + + mkdir -p "$NFT_DIR" + + # Build optional elements lines (nftables rejects empty elements = {}) + local proxy_elements reject_elements dscp_elements fwmark_elements + proxy_elements="$(render_ip_elements "$proxy_lan_ips")" + reject_elements="$(render_ip_elements "$reject_lan_ips")" + dscp_elements="$(render_token_elements "$bypass_dscp")" + fwmark_elements="$(merge_fwmark_tokens "$bypass_fwmark")" + tcp_match="$(render_port_match tcp "$proxy_tcp_dport")" + udp_match="$(render_port_match udp "$proxy_udp_dport")" + + { + printf 'set clashoo_localnetwork {\n\ttype ipv4_addr;\n\tflags interval;\n\tauto-merge;\n' + printf '\telements = { 0.0.0.0/8, 10.0.0.0/8, 100.64.0.0/10, 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 }\n}\n\n' + + append_set_from_file_or_empty "$GEOIP_CN_NFT" clashoo_china ipv4_addr + append_set_from_file_or_empty "$GEOIP6_CN_NFT" clashoo_china6 ipv6_addr + + printf 'set clash_proxy_lan {\n\ttype ipv4_addr;\n\tflags interval;\n\tauto-merge;\n' + [ -n "$proxy_elements" ] && printf '\telements = { %s }\n' "$proxy_elements" + printf '}\n\n' + + printf 'set clash_reject_lan {\n\ttype ipv4_addr;\n\tflags interval;\n\tauto-merge;\n' + [ -n "$reject_elements" ] && printf '\telements = { %s }\n' "$reject_elements" + printf '}\n\n' + + if grouped_acl_enabled && [ "$tcp_mode" = "tun" ]; then + printf 'chain clashoo_acl_tun_tcp {\n' + render_acl_proxy_rules tun tcp "$tcp_match" "$tproxy_port" + printf '}\n\n' + fi + if grouped_acl_enabled && [ "$udp_mode" = "tun" ]; then + printf 'chain clashoo_acl_tun_udp {\n' + render_acl_proxy_rules tun udp "$udp_match" "$tproxy_port" + printf '}\n' + fi + } > "$SETS_RULES" + + : > "$OUTPUT_RULES" + + +# DNS hijack rules go atop DSTNAT_RULES: must run for all TCP modes + # (redirect/tproxy/tun) and BEFORE proxy redirect rules so port 53 is not stolen. + render_dns_hijack "$access_control" > "$DSTNAT_RULES" + + +# TCP rules: redirect mode appends proxy redirect; tproxy/tun skip this chain + case "$tcp_mode" in + redirect) + tcp_match="$(render_port_match tcp "$proxy_tcp_dport")" + cat >> "$DSTNAT_RULES" < "$MANGLE_RULES" + else + : > "$MANGLE_RULES" + fi +} + +apply_rules() { + generate_rules + ensure_firewall_include clash_fw4_sets "$SETS_RULES" '' table-pre + ensure_firewall_include clash_fw4_dstnat "$DSTNAT_RULES" dstnat + remove_firewall_include clash_fw4_output + if [ -s "$MANGLE_RULES" ]; then + _route_table_dec="$((PROXY_ROUTE_TABLE))" + ensure_firewall_include clash_fw4_mangle "$MANGLE_RULES" mangle_prerouting + ip rule show 2>/dev/null | grep -q "fwmark ${PROXY_FWMARK}.*lookup ${_route_table_dec}" || + ip rule add fwmark "$PROXY_FWMARK" table "$PROXY_ROUTE_TABLE" >/dev/null 2>&1 || true + ip route show table "$PROXY_ROUTE_TABLE" 2>/dev/null | grep -q 'local 0.0.0.0/0 dev lo' || + ip route add local 0.0.0.0/0 dev lo table "$PROXY_ROUTE_TABLE" >/dev/null 2>&1 || true + if bool_enabled "$(config_ipv6_proxy)"; then + ip -6 rule show 2>/dev/null | grep -q "fwmark ${PROXY_FWMARK}.*lookup ${_route_table_dec}" || + ip -6 rule add fwmark "$PROXY_FWMARK" table "$PROXY_ROUTE_TABLE" >/dev/null 2>&1 || true + ip -6 route show table "$PROXY_ROUTE_TABLE" 2>/dev/null | grep -q 'local default dev lo' || + ip -6 route add local ::/0 dev lo table "$PROXY_ROUTE_TABLE" >/dev/null 2>&1 || true + else + ip -6 rule del fwmark "$PROXY_FWMARK" table "$PROXY_ROUTE_TABLE" >/dev/null 2>&1 || true + ip -6 route del local ::/0 dev lo table "$PROXY_ROUTE_TABLE" >/dev/null 2>&1 || true + fi + else + remove_firewall_include clash_fw4_mangle + ip rule del fwmark "$PROXY_FWMARK" table "$PROXY_ROUTE_TABLE" >/dev/null 2>&1 || true + ip route del local 0.0.0.0/0 dev lo table "$PROXY_ROUTE_TABLE" >/dev/null 2>&1 || true + ip -6 rule del fwmark "$PROXY_FWMARK" table "$PROXY_ROUTE_TABLE" >/dev/null 2>&1 || true + ip -6 route del local ::/0 dev lo table "$PROXY_ROUTE_TABLE" >/dev/null 2>&1 || true + fi + /etc/init.d/firewall restart >/dev/null 2>&1 || /etc/init.d/firewall reload >/dev/null 2>&1 || true + apply_acl_bypass_rules + apply_local_output_rule + apply_block_quic_rule +} + +remove_rules() { + remove_acl_bypass_rules + remove_local_output_rule + remove_block_quic_rule + remove_firewall_include clash_fw4_sets + remove_firewall_include clash_fw4_dstnat + remove_firewall_include clash_fw4_output + remove_firewall_include clash_fw4_mangle + remove_firewall_include clash_fw4_forward + uci commit firewall >/dev/null 2>&1 || true + rm -f "$SETS_RULES" "$DSTNAT_RULES" "$OUTPUT_RULES" "$MANGLE_RULES" + ip rule del fwmark "$PROXY_FWMARK" table "$PROXY_ROUTE_TABLE" >/dev/null 2>&1 || true + ip route del local 0.0.0.0/0 dev lo table "$PROXY_ROUTE_TABLE" >/dev/null 2>&1 || true + ip -6 rule del fwmark "$PROXY_FWMARK" table "$PROXY_ROUTE_TABLE" >/dev/null 2>&1 || true + ip -6 route del local ::/0 dev lo table "$PROXY_ROUTE_TABLE" >/dev/null 2>&1 || true + /etc/init.d/firewall restart >/dev/null 2>&1 || /etc/init.d/firewall reload >/dev/null 2>&1 || true +} + +case "${1:-}" in + apply) + apply_rules + ;; + remove) + remove_rules + ;; + *) + echo "Usage: $0 {apply|remove}" >&2 + exit 1 + ;; +esac diff --git a/clashoo/files/usr/share/clashoo/net/log_format.awk b/clashoo/files/usr/share/clashoo/net/log_format.awk new file mode 100644 index 00000000..0813c3b7 --- /dev/null +++ b/clashoo/files/usr/share/clashoo/net/log_format.awk @@ -0,0 +1,43 @@ +# clashoo 运行日志格式化 +# 输出: MM-DD HH:MM:SS msg + +# mihomo 原生行: time="YYYY-MM-DDTHH:MM:SS..." level=... msg="..." +/^time="[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9]/ { + # extract YYYY-MM-DD and HH:MM:SS + ts_date = substr($0, 12, 5) # MM-DD + ts_time = substr($0, 18, 8) # HH:MM:SS + utc_h = substr(ts_time, 1, 2) + 0 + cst_h = (utc_h + 8) % 24 + ts = sprintf("%s %02d:%s", ts_date, cst_h, substr(ts_time, 4)) + + prefix = "" + if (match($0, /level=warning /)) prefix = " [warn]" + else if (match($0, /level=error /)) prefix = " [err]" + else if (match($0, /level=fatal /)) prefix = " [fatal]" + + i = index($0, "msg=\"") + if (i > 0) { + rest = substr($0, i + 5) + sub(/"[[:space:]]*$/, "", rest) + print ts prefix " " rest + next + } + print ts " " $0 + next +} + +# log_msg 行: " YYYY-MM-DD HH:MM:SS - msg" → "MM-DD HH:MM:SS msg" +/^[[:space:]]+[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9][[:space:]]+[0-9][0-9]:[0-9][0-9]:[0-9][0-9]/ { + gsub(/^[[:space:]]+/, "") # trim leading space + ts_date = substr($0, 6, 5) # MM-DD + ts_time = substr($0, 12, 8) # HH:MM:SS + # remove "YYYY-MM-DD HH:MM:SS - " prefix + sub(/^[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9][[:space:]]+[0-9][0-9]:[0-9][0-9]:[0-9][0-9][[:space:]]*-[[:space:]]*/, "") + print ts_date " " ts_time " " $0 + next +} + +# 空行丢弃 +NF == 0 { next } + +{ print } diff --git a/clashoo/files/usr/share/clashoo/nftables/geoip6_cn.nft b/clashoo/files/usr/share/clashoo/nftables/geoip6_cn.nft new file mode 100644 index 00000000..5cd2612d --- /dev/null +++ b/clashoo/files/usr/share/clashoo/nftables/geoip6_cn.nft @@ -0,0 +1,3496 @@ +set clashoo_china6 { + type ipv6_addr; + flags interval; + auto-merge; + elements = { + 2001:250::/30, + 2001:254::/33, + 2001:255::/32, + 2001:7fa:5::/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::/45, + 2001:dc7:9::/48, + 2001:dc7:a::/47, + 2001:dc7:c::/46, + 2001:dc7:10::/44, + 2001:dc7:20::/43, + 2001:dc7:40::/42, + 2001:dc7:80::/41, + 2001:dc7:100::/40, + 2001:dc7:200::/39, + 2001:dc7:400::/38, + 2001:dc7:800::/37, + 2001:dc7:1000::/36, + 2001:dc7:2000::/35, + 2001:dc7:4000::/34, + 2001:dc7:8000::/33, + 2001:dd8:1::/48, + 2001:dd9::/48, + 2001:df5:7800::/48, + 2001:4510:400::/40, + 2001:4510:1480::/41, + 2001:4511:1480::/41, + 2400:1040::/32, + 2400:1160::/32, + 2400:12c0::/32, + 2400:1340::/32, + 2400:1380::/32, + 2400:15c0::/32, + 2400:1640::/32, + 2400:16c0::/32, + 2400:1740::/32, + 2400:17c0::/32, + 2400:1840::/32, + 2400:18c0::/32, + 2400:1940::/32, + 2400:19a0::/32, + 2400:19c0::/32, + 2400:1a40::/32, + 2400:1ac0::/32, + 2400:1b40::/32, + 2400:1cc0::/32, + 2400:1d40::/32, + 2400:1dc0::/32, + 2400:1e40::/32, + 2400:1ec0::/32, + 2400:1f40::/32, + 2400:1fc0::/32, + 2400:3040::/32, + 2400:3140::/32, + 2400:3160::/32, + 2400:31c0::/32, + 2400:3200::/32, + 2400:32c0::/32, + 2400:3340::/32, + 2400:33c0::/32, + 2400:3440::/32, + 2400:34c0::/32, + 2400:3540::/32, + 2400:35c0::/32, + 2400:3600::/32, + 2400:3640::/32, + 2400:3660::/32, + 2400:36c0::/32, + 2400:38c0::/32, + 2400:39c0::/32, + 2400:3a00::/32, + 2400:3a40::/32, + 2400:3b40::/32, + 2400:3c40::/32, + 2400:3cc0::/32, + 2400:3e00::/32, + 2400:3f40::/32, + 2400:3f60::/46, + 2400:3f60:5::/48, + 2400:3f60:6::/47, + 2400:3f60:8::/45, + 2400:3f60:13::/48, + 2400:3f60:14::/46, + 2400:3f60:18::/45, + 2400:3f60:20::/43, + 2400:3f60:40::/42, + 2400:3f60:80::/41, + 2400:3f60:100::/40, + 2400:3f60:200::/39, + 2400:3f60:400::/38, + 2400:3f60:800::/37, + 2400:3f60:1000::/36, + 2400:3f60:2000::/35, + 2400:3f60:4000::/34, + 2400:3f60:8000::/33, + 2400:3fc0::/32, + 2400:4440::/32, + 2400:44c0::/32, + 2400:4540::/32, + 2400:4600::/32, + 2400:4640::/32, + 2400:46c0::/32, + 2400:4920::/32, + 2400:4bc0::/32, + 2400:4e00::/32, + 2400:4e40::/32, + 2400:5080::/32, + 2400:5280:1400::/38, + 2400:5280:1c00::/38, + 2400:5280:3400::/38, + 2400:5280:5000::/36, + 2400:5280:6000::/35, + 2400:5280:8000::/34, + 2400:5280:c000::/35, + 2400:5280:e000::/36, + 2400:5280:f000::/37, + 2400:5280:f800::/38, + 2400:5400::/32, + 2400:5580::/32, + 2400:55c0::/32, + 2400:55e0::/32, + 2400:5600::/32, + 2400:5640::/32, + 2400:56c0::/32, + 2400:57c0::/32, + 2400:5840::/32, + 2400:5a00::/32, + 2400:5a40::/32, + 2400:5a60::/41, + 2400:5a60:80::/42, + 2400:5a60:c0::/43, + 2400:5a60:e0::/44, + 2400:5a60:f0::/45, + 2400:5a60:f8::/46, + 2400:5a60:fc::/47, + 2400:5a60:fe::/48, + 2400:5a60:100::/40, + 2400:5a60:200::/39, + 2400:5a60:400::/38, + 2400:5a60:800::/37, + 2400:5a60:1000::/36, + 2400:5a60:2000::/35, + 2400:5a60:4000::/34, + 2400:5a60:8001::/48, + 2400:5a60:8002::/47, + 2400:5a60:8004::/46, + 2400:5a60:8008::/45, + 2400:5a60:8010::/44, + 2400:5a60:8020::/43, + 2400:5a60:8040::/42, + 2400:5a60:8080::/41, + 2400:5a60:8100::/40, + 2400:5a60:8200::/39, + 2400:5a60:8400::/38, + 2400:5a60:8800::/37, + 2400:5a60:9000::/36, + 2400:5a60:a000::/35, + 2400:5a60:c000::/34, + 2400:5ac0::/32, + 2400:5b40::/32, + 2400:5bc0::/32, + 2400:5c40::/32, + 2400:5c80::/32, + 2400:5cc0::/32, + 2400:5e20::/32, + 2400:5e80::/32, + 2400:5ee0::/32, + 2400:5fc0::/32, + 2400:6000::/32, + 2400:6040::/32, + 2400:60c0::/32, + 2400:61c0::/32, + 2400:6200::/32, + 2400:6600::/32, + 2400:6640::/32, + 2400:66a0::/32, + 2400:66c0::/32, + 2400:66e0::/32, + 2400:6740::/32, + 2400:67a0::/32, + 2400:67c0::/32, + 2400:6840::/32, + 2400:68c0::/32, + 2400:6940::/32, + 2400:69c0::/32, + 2400:6a00::/32, + 2400:6a40::/32, + 2400:6ac0::/32, + 2400:6b40::/32, + 2400:6bc0::/32, + 2400:6c40::/32, + 2400:6cc0::/32, + 2400:6d40::/32, + 2400:6da0::/32, + 2400:6dc0::/32, + 2400:6e00::/32, + 2400:6e40::/32, + 2400:6e60::/32, + 2400:6ec0::/32, + 2400:6f40::/32, + 2400:6f80::/32, + 2400:6fc0::/32, + 2400:7040::/32, + 2400:70a0::/32, + 2400:7100::/32, + 2400:7140::/32, + 2400:71c0::/32, + 2400:7200::/32, + 2400:7240::/32, + 2400:72c0::/32, + 2400:7340::/32, + 2400:73c0::/32, + 2400:73e0::/32, + 2400:7440::/32, + 2400:74c0::/32, + 2400:7540::/32, + 2400:75a0::/28, + 2400:75c0::/32, + 2400:7640::/32, + 2400:7680::/32, + 2400:76c0::/32, + 2400:7740::/32, + 2400:77c0::/32, + 2400:79c0::/32, + 2400:7ae0::/32, + 2400:7bc0::/32, + 2400:7f80::/32, + 2400:7fc0::/32, + 2400:8080::/32, + 2400:8200::/31, + 2400:82c0::/32, + 2400:8580::/32, + 2400:8600::/32, + 2400:86a0::/32, + 2400:86e0::/32, + 2400:8780::/32, + 2400:87c0::/32, + 2400:8840::/32, + 2400:8920::/32, + 2400:8980::/32, + 2400:89c0::/32, + 2400:8be0::/32, + 2400:8ce0::/32, + 2400:8e00::/32, + 2400:8e60::/32, + 2400:8f00::/32, + 2400:8f60::/32, + 2400:8fc0::/32, + 2400:9020::/32, + 2400:9040::/32, + 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:93e0::/32, + 2400:9520::/32, + 2400:9580::/32, + 2400:95c0::/32, + 2400:95e0::/32, + 2400:9600::/32, + 2400:9620::/32, + 2400:98c0::/32, + 2400:9960::/32, + 2400:99e0::/32, + 2400:9a00::/32, + 2400:9ca0::/32, + 2400:9e00::/32, + 2400:a320::/32, + 2400:a380::/32, + 2400:a420::/32, + 2400:a480::/32, + 2400:a5a0::/32, + 2400:a6a0::/39, + 2400:a6a0:300::/40, + 2400:a6a0:400::/38, + 2400:a6a0:800::/37, + 2400:a6a0:1000::/36, + 2400:a6a0:2000::/35, + 2400:a6a0:4000::/34, + 2400:a6a0:8000::/33, + 2400:a780::/32, + 2400:a860::/32, + 2400:a8a0::/32, + 2400:a8c0::/32, + 2400:a900::/32, + 2400:a980::/29, + 2400:a9a0::/32, + 2400:abc0::/32, + 2400:ae00::/32, + 2400:b200::/32, + 2400:b500::/32, + 2400:b600::/32, + 2400:b620::/32, + 2400:b6c0::/32, + 2400:b700::/32, + 2400:b9a0::/32, + 2400:b9c0::/32, + 2400:ba00::/32, + 2400:be00::/32, + 2400:bf00::/32, + 2400:c200::/32, + 2400:c380::/32, + 2400:c840::/32, + 2400:c8c0::/32, + 2400:c940::/32, + 2400:c9c0::/32, + 2400:ca40::/32, + 2400:cac0::/32, + 2400:cb40::/32, + 2400:cb80::/33, + 2400:cb80:8000::/40, + 2400:cb80:8100::/43, + 2400:cb80:8120::/44, + 2400:cb80:8130::/45, + 2400:cb80:8138::/47, + 2400:cb80:813a::/48, + 2400:cb80:813c::/47, + 2400:cb80:813e::/48, + 2400:cb80:8140::/42, + 2400:cb80:8180::/41, + 2400:cb80:8200::/39, + 2400:cb80:8400::/38, + 2400:cb80:8800::/37, + 2400:cb80:9000::/36, + 2400:cb80:a000::/35, + 2400:cb80:c000::/34, + 2400:cbc0::/32, + 2400:cc40::/32, + 2400:cc80::/32, + 2400:ccc0::/32, + 2400:cd40::/32, + 2400:cda0::/32, + 2400:cdc0::/32, + 2400:ce00::/32, + 2400:ce40::/32, + 2400:cf40::/32, + 2400:cfc0::/32, + 2400:d0a0::/32, + 2400:d0c0::/32, + 2400:d100::/32, + 2400:d160::/32, + 2400:d1c0::/32, + 2400:d200::/32, + 2400:d300::/32, + 2400:d440::/32, + 2400:d600::/32, + 2400:d6a0::/32, + 2400:d6c0::/32, + 2400:d720::/32, + 2400:d780::/32, + 2400:d7a0::/32, + 2400:da00::/32, + 2400:da60::/32, + 2400:dd00::/28, + 2400:dd40::/32, + 2400:dda0::/32, + 2400:de00::/32, + 2400:de80::/32, + 2400:dee0::/32, + 2400:e0c0::/32, + 2400:e680::/32, + 2400:e7e0::/32, + 2400:e880::/32, + 2400:ebc0::/32, + 2400:ed60::/32, + 2400:eda0::/32, + 2400:edc0::/32, + 2400:ee00::/32, + 2400:eec0::/32, + 2400:ef40::/32, + 2400:f480::/32, + 2400:f5c0::/32, + 2400:f6e0::/32, + 2400:f720::/32, + 2400:f7c0::/32, + 2400:f840::/32, + 2400:f860::/32, + 2400:f980::/32, + 2400:fac0::/32, + 2400:fb40::/32, + 2400:fb60::/32, + 2400:fbc0::/32, + 2400:fc40::/32, + 2400:fcc0::/32, + 2400:fe00::/32, + 2401:20:100::/40, + 2401:20:200::/39, + 2401:20:400::/38, + 2401:20:800::/37, + 2401:20:1000::/39, + 2401:20:1200::/40, + 2401:20:1300::/43, + 2401:20:1320::/44, + 2401:20:1334::/46, + 2401:20:1338::/45, + 2401:20:1340::/42, + 2401:20:1380::/41, + 2401:20:1400::/38, + 2401:20:1800::/37, + 2401:20:2000::/40, + 2401:20:2100::/46, + 2401:20:2108::/45, + 2401:20:2110::/44, + 2401:20:2120::/43, + 2401:20:2140::/42, + 2401:20:2180::/41, + 2401:20:2200::/39, + 2401:20:2400::/38, + 2401:20:2800::/37, + 2401:20:3000::/36, + 2401:20:4000::/34, + 2401:20:8000::/33, + 2401:60::/32, + 2401:80::/32, + 2401:140::/32, + 2401:1c0::/32, + 2401:540::/32, + 2401:620::/32, + 2401:7c0::/32, + 2401:800::/32, + 2401:9c0::/32, + 2401:a00::/32, + 2401:a40::/32, + 2401:ac0::/32, + 2401:b40::/32, + 2401:ba0::/32, + 2401:bc0::/32, + 2401:c40::/32, + 2401:cc0::/32, + 2401:d40::/32, + 2401:e00::/32, + 2401:1000::/32, + 2401:1160::/32, + 2401:11c0::/32, + 2401:1200::/32, + 2401:12c0::/32, + 2401:1320::/32, + 2401:15c0::/32, + 2401:18c0::/32, + 2401:18e0::/28, + 2401:1940::/32, + 2401:19c0::/32, + 2401:1a40::/32, + 2401:1ac0::/32, + 2401:1c60::/32, + 2401:1ce0::/32, + 2401:1d40::/32, + 2401:1da0::/43, + 2401:1da0:30::/44, + 2401:1da0:40::/42, + 2401:1da0:80::/41, + 2401:1da0:100::/40, + 2401:1da0:200::/39, + 2401:1da0:400::/38, + 2401:1da0:800::/37, + 2401:1da0:1000::/36, + 2401:1da0:2000::/35, + 2401:1da0:4000::/34, + 2401:1da0:8000::/33, + 2401:1dc0::/32, + 2401:1de0::/32, + 2401:1e00::/32, + 2401:1ec0::/32, + 2401:1f40::/32, + 2401:2040::/32, + 2401:2080::/32, + 2401:23c0::/32, + 2401:2600::/32, + 2401:2780::/32, + 2401:2980::/32, + 2401:2a00:f000::/43, + 2401:2b40::/32, + 2401:2e00::/32, + 2401:2e20::/32, + 2401:3100::/32, + 2401:3380::/32, + 2401:33c0::/32, + 2401:3440::/32, + 2401:3480::/32, + 2401:34a0::/31, + 2401:34c0::/32, + 2401:3640::/32, + 2401:3780::/32, + 2401:3800::/32, + 2401:3880::/32, + 2401:3980::/32, + 2401:3a00::/32, + 2401:3a80::/32, + 2401:3b80::/32, + 2401:3c20::/32, + 2401:3c80::/32, + 2401:3d80::/32, + 2401:3e80::/32, + 2401:3f80::/32, + 2401:4080::/32, + 2401:4180::/32, + 2401:4280::/32, + 2401:4380::/32, + 2401:4480::/32, + 2401:4580::/32, + 2401:4680::/32, + 2401:4780::/32, + 2401:4880::/32, + 2401:4a80::/32, + 2401:4b00::/32, + 2401:4f80::/32, + 2401:5680::/32, + 2401:58a0::/32, + 2401:59c0::/32, + 2401:5b40::/32, + 2401:5c20::/32, + 2401:5c60::/32, + 2401:5c80::/32, + 2401:5fa0::/32, + 2401:70e0::/32, + 2401:7180::/32, + 2401:71c0::/32, + 2401:7240::/32, + 2401:7320::/32, + 2401:7360::/32, + 2401:73a0::/32, + 2401:7580::/32, + 2401:7660::/32, + 2401:7680::/32, + 2401:7700::/32, + 2401:7780::/32, + 2401:77e0::/32, + 2401:7820::/32, + 2401:7880::/32, + 2401:78e0::/32, + 2401:7980::/32, + 2401:7a00::/32, + 2401:7a80::/32, + 2401:7b80::/32, + 2401:7bc0::/32, + 2401:7c80::/32, + 2401:7cc0::/32, + 2401:7ce0::/32, + 2401:7d40::/32, + 2401:7d80::/32, + 2401:7e00::/32, + 2401:7f80::/32, + 2401:8200::/32, + 2401:82c0::/32, + 2401:8380::/32, + 2401:8540::/32, + 2401:8600::/32, + 2401:8680::/34, + 2401:8680:4000::/46, + 2401:8680:4008::/45, + 2401:8680:4010::/44, + 2401:8680:4020::/43, + 2401:8680:4040::/42, + 2401:8680:4080::/41, + 2401:8680:4100::/48, + 2401:8680:4103::/48, + 2401:8680:4104::/46, + 2401:8680:4108::/45, + 2401:8680:4110::/44, + 2401:8680:4120::/43, + 2401:8680:4140::/42, + 2401:8680:4180::/41, + 2401:8680:4200::/39, + 2401:8680:4400::/38, + 2401:8680:4800::/37, + 2401:8680:5000::/36, + 2401:8680:6000::/35, + 2401:8680:8000::/33, + 2401:8720::/32, + 2401:87e0::/32, + 2401:8820::/31, + 2401:8840::/32, + 2401:8be0::/32, + 2401:8d00::/47, + 2401:8d00:3::/48, + 2401:8d00:4::/46, + 2401:8d00:8::/45, + 2401:8d00:10::/46, + 2401:8d00:17::/48, + 2401:8d00:18::/45, + 2401:8d00:20::/43, + 2401:8d00:40::/42, + 2401:8d00:80::/41, + 2401:8d00:100::/40, + 2401:8d00:200::/39, + 2401:8d00:400::/38, + 2401:8d00:800::/37, + 2401:8d00:1000::/36, + 2401:8d00:2000::/35, + 2401:8d00:4000::/34, + 2401:8d00:8000::/33, + 2401:8da0::/32, + 2401:8f40::/32, + 2401:8fc0::/32, + 2401:90a0::/32, + 2401:9260::/32, + 2401:92a0::/32, + 2401:92e0::/32, + 2401:9340::/32, + 2401:95e0::/32, + 2401:9600::/32, + 2401:96c0::/32, + 2401:96e0::/32, + 2401:9720::/32, + 2401:9740::/32, + 2401:97a0::/32, + 2401:98c0::/32, + 2401:9a00::/32, + 2401:9ac0::/32, + 2401:9b20::/31, + 2401:9b40::/32, + 2401:9b60::/32, + 2401:9bc0::/32, + 2401:9ca0::/32, + 2401:9d20::/32, + 2401:9dc0::/32, + 2401:9e20::/32, + 2401:9e40::/32, + 2401:9f80::/32, + 2401:9fa0::/32, + 2401:a140::/32, + 2401:a2e0::/32, + 2401:a340::/32, + 2401:a3a0::/32, + 2401:a3c0::/32, + 2401:a4c0::/32, + 2401:a4e0::/32, + 2401:a540::/32, + 2401:a5c0::/32, + 2401:a620::/32, + 2401:a640::/32, + 2401:a6e0::/32, + 2401:a720::/32, + 2401:a940::/32, + 2401:a980::/32, + 2401:a9a0::/32, + 2401:aa00::/32, + 2401:aa20::/32, + 2401:aa40::/32, + 2401:ab60::/32, + 2401:aba0::/32, + 2401:acc0::/32, + 2401:ad40::/32, + 2401:adc0::/32, + 2401:afa0::/32, + 2401:b040::/32, + 2401:b180::/32, + 2401:b220::/32, + 2401:b340::/32, + 2401:b360::/32, + 2401:b400::/32, + 2401:b4c0::/32, + 2401:b4e0::/32, + 2401:b540::/32, + 2401:b580::/32, + 2401:b5a0::/32, + 2401:b600::/32, + 2401:b680::/32, + 2401:b6c0::/32, + 2401:b6e0::/32, + 2401:b7c0::/32, + 2401:b940::/32, + 2401:ba00::/32, + 2401:ba40::/32, + 2401:bb20::/32, + 2401:bc60::/31, + 2401:bda0::/32, + 2401:be00::/32, + 2401:bf20::/32, + 2401:c020::/32, + 2401:c200::/32, + 2401:c540::/32, + 2401:c600::/32, + 2401:c640::/32, + 2401:c6c0::/32, + 2401:c840::/32, + 2401:c8c0::/32, + 2401:ca00::/32, + 2401:ca20::/32, + 2401:cb80::/32, + 2401:cbe0::/32, + 2401:cc00::/32, + 2401:cc60::/32, + 2401:ce00::/32, + 2401:cf40::/32, + 2401:cfc0::/32, + 2401:cfe0::/32, + 2401:d060::/32, + 2401:d0c0::/32, + 2401:d0e0::/32, + 2401:d140::/32, + 2401:d180::/32, + 2401:d2c0::/32, + 2401:d340::/32, + 2401:d420::/32, + 2401:d780::/32, + 2401:d7e0::/32, + 2401:d8e0::/32, + 2401:d920::/28, + 2401:da00::/32, + 2401:dbe0::/32, + 2401:dd20::/32, + 2401:dd60::/32, + 2401:de00::/32, + 2401:dfe0::/32, + 2401:e020::/32, + 2401:e080::/32, + 2401:e0c0::/32, + 2401:e140::/32, + 2401:e240::/32, + 2401:e2c0::/32, + 2401:e340::/32, + 2401:e360::/32, + 2401:e620::/32, + 2401:e840::/32, + 2401:e8c0::/32, + 2401:e940::/32, + 2401:e9c0::/32, + 2401:ec00::/32, + 2401:ec40::/32, + 2401:f0a0::/32, + 2401:f0e0::/32, + 2401:f220::/32, + 2401:f300::/32, + 2401:f320::/32, + 2401:f3e0::/32, + 2401:f7c0::/32, + 2401:f860::/46, + 2401:f860:4::/47, + 2401:f860:6::/48, + 2401:f860:8::/47, + 2401:f860:a::/48, + 2401:f860:c::/46, + 2401:f860:10::/44, + 2401:f860:20::/43, + 2401:f860:40::/42, + 2401:f860:80::/45, + 2401:f860:89::/48, + 2401:f860:8a::/47, + 2401:f860:8c::/46, + 2401:f860:91::/48, + 2401:f860:93::/48, + 2401:f860:95::/48, + 2401:f860:96::/48, + 2401:f860:98::/45, + 2401:f860:a0::/43, + 2401:f860:c0::/43, + 2401:f860:e0::/44, + 2401:f860:f0::/46, + 2401:f860:f4::/47, + 2401:f860:f7::/48, + 2401:f860:f8::/45, + 2401:f860:200::/39, + 2401:f860:400::/38, + 2401:f860:800::/37, + 2401:f860:1000::/36, + 2401:f860:2000::/35, + 2401:f860:4000::/34, + 2401:f860:8000::/33, + 2401:fa80::/32, + 2401:fb80::/32, + 2401:fc60::/32, + 2401:fc80::/32, + 2401:ffc0::/32, + 2402:440::/32, + 2402:5c0::/32, + 2402:840::/32, + 2402:a60::/32, + 2402:c20::/32, + 2402:c60::/32, + 2402:e00::/32, + 2402:fc0::/32, + 2402:1000::/32, + 2402:1160::/32, + 2402:1440::/32, + 2402:14c0::/32, + 2402:1520::/32, + 2402:1600::/32, + 2402:16e0::/32, + 2402:1740::/32, + 2402:18a0::/32, + 2402:19c0::/32, + 2402:1be0::/32, + 2402:1c20::/32, + 2402:1f80::/32, + 2402:2000::/32, + 2402:20e0::/33, + 2402:20e0:8000::/34, + 2402:20e0:c000::/35, + 2402:20e0:e000::/36, + 2402:20e0:f001::/48, + 2402:20e0:f002::/47, + 2402:20e0:f004::/46, + 2402:20e0:f008::/45, + 2402:20e0:f010::/44, + 2402:20e0:f020::/43, + 2402:20e0:f040::/42, + 2402:20e0:f080::/41, + 2402:20e0:f100::/40, + 2402:20e0:f200::/39, + 2402:20e0:f400::/38, + 2402:20e0:f800::/37, + 2402:2280::/32, + 2402:2440::/32, + 2402:24c0::/32, + 2402:2540::/32, + 2402:2620::/32, + 2402:2640::/32, + 2402:2760::/32, + 2402:2a00::/32, + 2402:2b80::/32, + 2402:2bc0::/32, + 2402:2ca0::/32, + 2402:2d00::/32, + 2402:2d80::/32, + 2402:2e60::/32, + 2402:2e80::/32, + 2402:2f40::/32, + 2402:3040::/32, + 2402:3140::/32, + 2402:3180:4::/46, + 2402:3180:8::/45, + 2402:3180:10::/44, + 2402:3180:20::/43, + 2402:3180:40::/42, + 2402:3180:80::/41, + 2402:3180:100::/40, + 2402:3180:200::/39, + 2402:3180:400::/38, + 2402:3180:800::/37, + 2402:3180:1000::/36, + 2402:3180:2000::/35, + 2402:3180:4000::/34, + 2402:31c0::/32, + 2402:3240::/32, + 2402:32e0::/32, + 2402:3320::/32, + 2402:33a0::/32, + 2402:33c0::/32, + 2402:34e0::/32, + 2402:36e0::/32, + 2402:39c0::/32, + 2402:3a40::/32, + 2402:3ac0::/32, + 2402:3ba0::/32, + 2402:3c00::/32, + 2402:3d20:2000::/35, + 2402:3d20:4000::/34, + 2402:3d20:8000::/33, + 2402:3de0::/32, + 2402:3e00::/32, + 2402:3ec0::/32, + 2402:3f80::/32, + 2402:4060::/32, + 2402:4140::/32, + 2402:42c0::/32, + 2402:4340::/32, + 2402:43c0::/32, + 2402:4440::/32, + 2402:4500::/32, + 2402:4540::/32, + 2402:4820::/32, + 2402:4a00::/32, + 2402:4a40::/32, + 2402:4a80::/32, + 2402:4ac0::/32, + 2402:4b80::/32, + 2402:4bc0::/32, + 2402:4be0::/32, + 2402:4c40::/32, + 2402:4d60::/32, + 2402:4d80::/32, + 2402:4e00::/32, + 2402:4ec0::/32, + 2402:4f80::/32, + 2402:50a0::/32, + 2402:5180::/32, + 2402:51a0::/32, + 2402:5340::/32, + 2402:5820::/32, + 2402:5880::/32, + 2402:5920::/32, + 2402:5940::/32, + 2402:59c0::/32, + 2402:5a40::/32, + 2402:5b40::/32, + 2402:5bc0::/32, + 2402:5d00::/32, + 2402:5e00::/32, + 2402:5e40::/32, + 2402:5ec0::/32, + 2402:5f20::/32, + 2402:5f40::/32, + 2402:6060::/32, + 2402:6280::/32, + 2402:6320::/32, + 2402:64c0::/32, + 2402:66c0::/32, + 2402:6740::/32, + 2402:67c0::/32, + 2402:6960::/32, + 2402:6a00::/32, + 2402:6b40::/32, + 2402:6bc0::/32, + 2402:6e00::/32, + 2402:6e80::/32, + 2402:6f40::/32, + 2402:6fc0::/32, + 2402:7040::/32, + 2402:7060::/32, + 2402:7080::/32, + 2402:70a0::/29, + 2402:70c0::/32, + 2402:7140::/32, + 2402:71c0::/32, + 2402:7240::/32, + 2402:72a0::/32, + 2402:72c0::/32, + 2402:73e0::/32, + 2402:7540::/32, + 2402:75c0::/32, + 2402:7740::/32, + 2402:7820::/32, + 2402:7d00::/32, + 2402:7d80::/32, + 2402:7da0::/32, + 2402:7e20::/32, + 2402:7ee0::/32, + 2402:8180::/32, + 2402:8300::/32, + 2402:8380::/32, + 2402:85c0::/32, + 2402:8800::/32, + 2402:8840::/32, + 2402:8900::/32, + 2402:8940::/32, + 2402:89c0::/32, + 2402:8b40::/32, + 2402:8bc0::/32, + 2402:8cc0::/32, + 2402:8d40::/32, + 2402:8f40::/32, + 2402:8f80::/32, + 2402:9240::/32, + 2402:92c0::/32, + 2402:93c0::/32, + 2402:9440::/32, + 2402:9480::/32, + 2402:94c0::/32, + 2402:9580::/32, + 2402:95c0::/32, + 2402:9680::/32, + 2402:96c0::/32, + 2402:9840::/32, + 2402:98c0::/32, + 2402:9940::/32, + 2402:9a80::/32, + 2402:9b80::/32, + 2402:9f80::/32, + 2402:9fc0::/32, + 2402:a080::/32, + 2402:a180::/32, + 2402:a200::/32, + 2402:a240::/32, + 2402:a280::/32, + 2402:a380::/32, + 2402:a640::/32, + 2402:a680::/32, + 2402:a6c0::/32, + 2402:a840::/32, + 2402:a880::/32, + 2402:aa80::/32, + 2402:ab80::/32, + 2402:ae00::/32, + 2402:ae40::/32, + 2402:aec0::/32, + 2402:af80::/32, + 2402:afc0::/32, + 2402:b080::/32, + 2402:b200::/32, + 2402:b440::/32, + 2402:b6c0::/32, + 2402:b880::/32, + 2402:b8c0::/32, + 2402:b940:100::/40, + 2402:b940:400::/38, + 2402:b940:800::/37, + 2402:b940:1000::/36, + 2402:b940:2000::/35, + 2402:b940:4000::/34, + 2402:b940:8000::/33, + 2402:b980::/32, + 2402:ba80::/32, + 2402:bac0::/32, + 2402:bbc0::/32, + 2402:bf80::/32, + 2402:c280::/32, + 2402:c3c0::/32, + 2402:c5c0::/32, + 2402:c9c0::/32, + 2402:cc40::/32, + 2402:cf00::/32, + 2402:cf40::/32, + 2402:d040::/32, + 2402:d140::/32, + 2402:d2c0:3::/48, + 2402:d2c0:4::/46, + 2402:d2c0:8::/45, + 2402:d2c0:10::/44, + 2402:d2c0:20::/43, + 2402:d2c0:40::/42, + 2402:d2c0:80::/41, + 2402:d2c0:100::/40, + 2402:d2c0:200::/39, + 2402:d2c0:400::/38, + 2402:d2c0:800::/37, + 2402:d2c0:1000::/36, + 2402:d2c0:2000::/35, + 2402:d2c0:4000::/34, + 2402:d2c0:8000::/33, + 2402:d300::/32, + 2402:d340::/32, + 2402:d380::/32, + 2402:d5c0::/32, + 2402:d6c0::/32, + 2402:d740::/32, + 2402:d780::/32, + 2402:d880::/32, + 2402:d980::/32, + 2402:da40::/32, + 2402:db40::/34, + 2402:db40:4000::/36, + 2402:db40:5000::/37, + 2402:db40:5800::/40, + 2402:db40:5901::/48, + 2402:db40:5902::/47, + 2402:db40:5904::/46, + 2402:db40:5908::/45, + 2402:db40:5910::/44, + 2402:db40:5920::/43, + 2402:db40:5940::/42, + 2402:db40:5980::/41, + 2402:db40:5a00::/39, + 2402:db40:5c00::/38, + 2402:db40:6000::/35, + 2402:db40:8000::/33, + 2402:dcc0::/32, + 2402:de40::/32, + 2402:dec0::/32, + 2402:df40::/32, + 2402:dfc0::/32, + 2402:e040::/32, + 2402:e0c0::/32, + 2402:e140::/32, + 2402:e2c0::/32, + 2402:e3c0::/32, + 2402:e480::/32, + 2402:e540::/32, + 2402:e680::/32, + 2402:e740::/32, + 2402:e780::/32, + 2402:e7c0::/37, + 2402:e7c0:800::/38, + 2402:e7c0:1000::/36, + 2402:e7c0:2000::/35, + 2402:e7c0:4000::/34, + 2402:e7c0:8000::/33, + 2402:e880::/32, + 2402:e980::/32, + 2402:eb80::/32, + 2402:ec80::/32, + 2402:ed80::/32, + 2402:ef40::/32, + 2402:ef80::/32, + 2402:f000::/32, + 2402:f140::/32, + 2402:f480::/32, + 2402:f540::/32, + 2402:f580::/32, + 2402:f780::/32, + 2402:f8c0::/32, + 2402:f980::/32, + 2402:f9c0::/32, + 2402:fac0::/32, + 2402:fcc0::/32, + 2402:ff40::/32, + 2402:ffc0::/32, + 2403:600::/32, + 2403:700::/32, + 2403:7c0::/32, + 2403:800::/31, + 2403:980::/32, + 2403:a80::/32, + 2403:b80::/32, + 2403:c80::/32, + 2403:d80::/32, + 2403:e80::/32, + 2403:f00::/32, + 2403:f80::/32, + 2403:fc0::/32, + 2403:1180::/32, + 2403:1340::/32, + 2403:1440::/32, + 2403:1580::/32, + 2403:16c0::/32, + 2403:17c0::/32, + 2403:1980::/32, + 2403:1b80::/32, + 2403:1c80::/32, + 2403:1d80::/32, + 2403:1dc0::/32, + 2403:1e80::/32, + 2403:1ec0::/36, + 2403:1ec0:1000::/37, + 2403:1ec0:1800::/40, + 2403:1ec0:1901::/48, + 2403:1ec0:1902::/47, + 2403:1ec0:1904::/46, + 2403:1ec0:1908::/45, + 2403:1ec0:1950::/44, + 2403:1ec0:1960::/43, + 2403:1ec0:1980::/41, + 2403:1ec0:1a00::/39, + 2403:1ec0:1c00::/38, + 2403:1ec0:2000::/35, + 2403:1ec0:4000::/34, + 2403:1ec0:8000::/33, + 2403:1f80::/32, + 2403:2040::/32, + 2403:2080::/32, + 2403:2180::/32, + 2403:2240::/32, + 2403:2280::/32, + 2403:2380::/32, + 2403:2440::/32, + 2403:24c0::/32, + 2403:2580::/32, + 2403:25c0::/32, + 2403:2680::/32, + 2403:2740::/32, + 2403:2780::/32, + 2403:28c0::/32, + 2403:2940::/32, + 2403:2a00::/32, + 2403:2a40::/32, + 2403:2ac0::/32, + 2403:2b40::/32, + 2403:2bc0::/32, + 2403:2cc0::/32, + 2403:2f40::/32, + 2403:2fc0::/32, + 2403:3040::/32, + 2403:30c0::/32, + 2403:3140::/32, + 2403:3280::/32, + 2403:32c0::/32, + 2403:3380::/32, + 2403:3480::/32, + 2403:3580::/32, + 2403:3640::/32, + 2403:3680::/32, + 2403:36c0::/32, + 2403:3740::/32, + 2403:3780::/32, + 2403:37c0::/32, + 2403:3840::/32, + 2403:3880::/32, + 2403:38c0::/32, + 2403:3940::/32, + 2403:3980::/32, + 2403:39c0::/32, + 2403:3b80::/32, + 2403:3c40::/32, + 2403:3c80::/32, + 2403:3cc0::/32, + 2403:3d40::/32, + 2403:3d80::/32, + 2403:3dc0::/32, + 2403:3e80::/32, + 2403:3ec0::/32, + 2403:3f80::/32, + 2403:4080::/32, + 2403:4180::/32, + 2403:4280::/32, + 2403:4300::/32, + 2403:4380::/32, + 2403:4580::/32, + 2403:4680::/32, + 2403:4780::/32, + 2403:4840::/32, + 2403:4880::/32, + 2403:4980::/32, + 2403:4a40::/32, + 2403:4a80::/32, + 2403:4b40::/32, + 2403:4b80::/32, + 2403:4c80::/32, + 2403:4cc0::/32, + 2403:4d80::/32, + 2403:4ec0::/32, + 2403:5080::/32, + 2403:5280::/32, + 2403:5380::/32, + 2403:54c0::/32, + 2403:5540::/32, + 2403:5580::/32, + 2403:5640::/32, + 2403:5780::/32, + 2403:5980::/32, + 2403:5a80::/32, + 2403:5b40::/32, + 2403:5b80::/32, + 2403:5c80::/32, + 2403:5d80::/32, + 2403:5e40::/32, + 2403:5e80::/32, + 2403:5ec0::/32, + 2403:5f80::/32, + 2403:5fc0::/32, + 2403:6080::/32, + 2403:6180::/32, + 2403:6280::/32, + 2403:62c0::/32, + 2403:6380::/44, + 2403:6380:20::/44, + 2403:6380:33::/48, + 2403:6380:34::/46, + 2403:6380:38::/45, + 2403:6380:41::/48, + 2403:6380:44::/46, + 2403:6380:48::/45, + 2403:6380:50::/44, + 2403:6380:60::/43, + 2403:6380:80::/41, + 2403:6380:100::/40, + 2403:6380:200::/39, + 2403:6380:400::/38, + 2403:6380:800::/37, + 2403:6380:1000::/36, + 2403:6380:2000::/35, + 2403:6380:4000::/34, + 2403:6380:8000::/33, + 2403:6580::/32, + 2403:6680::/32, + 2403:6740::/32, + 2403:6780::/32, + 2403:6880::/32, + 2403:6980::/32, + 2403:6a00::/32, + 2403:6c80::/32, + 2403:6d40::/32, + 2403:6d80::/32, + 2403:6e80::/32, + 2403:6f40::/32, + 2403:6fc0::/32, + 2403:7040::/32, + 2403:7080::/32, + 2403:7180::/32, + 2403:7280::/32, + 2403:7380::/32, + 2403:7480::/32, + 2403:7540::/32, + 2403:7580::/32, + 2403:76c0::/32, + 2403:7700::/32, + 2403:78c0::/32, + 2403:7a80::/32, + 2403:7b00::/32, + 2403:7d80::/32, + 2403:7e80::/32, + 2403:7f80::/32, + 2403:8080::/32, + 2403:8180::/32, + 2403:8280::/32, + 2403:8380::/32, + 2403:83c0::/32, + 2403:8480::/32, + 2403:8580::/32, + 2403:8880::/32, + 2403:8900::/32, + 2403:8980::/32, + 2403:8a40::/32, + 2403:8a80::/32, + 2403:8b00::/32, + 2403:8b80::/32, + 2403:8c00::/32, + 2403:8c80::/32, + 2403:8d00::/32, + 2403:8d80::/32, + 2403:9080::/32, + 2403:9180::/32, + 2403:9280::/32, + 2403:9380::/32, + 2403:9480::/32, + 2403:9580::/32, + 2403:9680::/32, + 2403:9780::/32, + 2403:9880::/32, + 2403:9a80::/32, + 2403:9ac0::/32, + 2403:9b00::/32, + 2403:9b40::/32, + 2403:9b80::/32, + 2403:9c80::/32, + 2403:9d00::/32, + 2403:9d80::/32, + 2403:9e40::/32, + 2403:9e80::/32, + 2403:9ec0::/32, + 2403:9f80::/32, + 2403:a100::/32, + 2403:a140::/32, + 2403:a200::/32, + 2403:a300::/32, + 2403:a480::/32, + 2403:a580::/32, + 2403:a680::/32, + 2403:a6c0::/32, + 2403:a780::/32, + 2403:a880::/32, + 2403:a940::/32, + 2403:a980::/32, + 2403:a9c0::/32, + 2403:aa40::/32, + 2403:aa80::/32, + 2403:ab80::/32, + 2403:ac00::/32, + 2403:af80::/32, + 2403:b080::/32, + 2403:b180::/32, + 2403:b280::/32, + 2403:b380::/32, + 2403:b400::/32, + 2403:b480::/32, + 2403:b580::/32, + 2403:b680::/32, + 2403:b780::/32, + 2403:b880::/32, + 2403:b980::/32, + 2403:ba40::/32, + 2403:c040::/32, + 2403:c080::/32, + 2403:c100::/32, + 2403:c140::/32, + 2403:c180::/32, + 2403:c3c0::/32, + 2403:c440::/32, + 2403:c480::/32, + 2403:c4c0::/32, + 2403:c980::/32, + 2403:cdc0::/32, + 2403:cf80::/32, + 2403:d080::/32, + 2403:d180::/32, + 2403:d280::/32, + 2403:d2c0::/32, + 2403:d380::/32, + 2403:d400::/32, + 2403:d440::/32, + 2403:d480::/32, + 2403:d580::/32, + 2403:d680::/32, + 2403:d780::/32, + 2403:d7c0::/32, + 2403:d880::/32, + 2403:d980::/32, + 2403:d9c0::/32, + 2403:da80::/32, + 2403:dac0::/32, + 2403:db00::/32, + 2403:db80::/32, + 2403:dc80::/32, + 2403:dd80::/32, + 2403:de80::/32, + 2403:df80::/32, + 2403:e080::/32, + 2403:e180::/32, + 2403:e280::/32, + 2403:e300::/32, + 2403:e480::/32, + 2403:e500::/32, + 2403:e580::/32, + 2403:e640::/32, + 2403:e680::/32, + 2403:e700::/32, + 2403:e780::/32, + 2403:e7c0::/32, + 2403:e880::/32, + 2403:e980::/32, + 2403:ea80::/32, + 2403:eac0::/32, + 2403:eb80::/32, + 2403:ec80::/32, + 2403:ed00::/32, + 2403:ed40::/32, + 2403:ed80::/32, + 2403:ee80::/32, + 2403:ef80::/32, + 2403:f080::/32, + 2403:f100::/32, + 2403:f180::/32, + 2403:f240::/32, + 2403:f280::/32, + 2403:f300::/32, + 2403:f380::/32, + 2403:f4c0::/32, + 2403:f580::/32, + 2403:f740::/32, + 2403:f8c0::/32, + 2403:f980::/32, + 2403:fb00::/32, + 2403:fb80::/32, + 2403:fc40::/32, + 2403:fe40::/32, + 2403:fe80::/32, + 2403:fec0::/32, + 2403:ff80::/32, + 2403:ffc0::/31, + 2404:100::/32, + 2404:158::/32, + 2404:240::/32, + 2404:280::/32, + 2404:440::/32, + 2404:480::/32, + 2404:680::/32, + 2404:a80::/32, + 2404:b80::/32, + 2404:bc0::/32, + 2404:c40::/32, + 2404:d80::/32, + 2404:f00::/32, + 2404:f80::/32, + 2404:1080::/32, + 2404:10c0::/32, + 2404:1180::/32, + 2404:14c0::/32, + 2404:1880::/32, + 2404:1c80::/32, + 2404:1cc0::/32, + 2404:1d80::/39, + 2404:1d80:200::/44, + 2404:1d80:210::/47, + 2404:1d80:212::/48, + 2404:1d80:214::/46, + 2404:1d80:218::/45, + 2404:1d80:220::/43, + 2404:1d80:240::/42, + 2404:1d80:280::/41, + 2404:1d80:300::/40, + 2404:1d80:400::/38, + 2404:1d80:800::/37, + 2404:1d80:1000::/36, + 2404:1d80:2000::/35, + 2404:1d80:4000::/34, + 2404:1d80:8000::/33, + 2404:1e80::/32, + 2404:1f40::/32, + 2404:21c0::/32, + 2404:2280:1000::/36, + 2404:2280:2000::/35, + 2404:2280:4000::/36, + 2404:30c0::/32, + 2404:3140::/32, + 2404:31c0::/32, + 2404:3240::/32, + 2404:32c0::/32, + 2404:3300::/32, + 2404:3340::/32, + 2404:3480::/32, + 2404:35c0::/32, + 2404:3640::/32, + 2404:36c0::/32, + 2404:3700::/32, + 2404:3740::/32, + 2404:37c0::/32, + 2404:3840::/32, + 2404:3940::/32, + 2404:3f40::/32, + 2404:41c0::/32, + 2404:4540::/32, + 2404:4740::/32, + 2404:4d00::/32, + 2404:4dc0::/32, + 2404:51c0::/32, + 2404:5640::/32, + 2404:5a80::/32, + 2404:5b00::/32, + 2404:5d00::/32, + 2404:6000::/32, + 2404:6100::/32, + 2404:6380::/32, + 2404:6500::/32, + 2404:65c0::/32, + 2404:6a40::/32, + 2404:6f80::/32, + 2404:7100::/32, + 2404:7180::/32, + 2404:71c0::/32, + 2404:7240::/32, + 2404:74c0::/32, + 2404:7600::/32, + 2404:7740::/32, + 2404:7940::/32, + 2404:7d00::/32, + 2404:8040::/32, + 2404:80c0::/32, + 2404:8140::/32, + 2404:81c0::/32, + 2404:8480::/32, + 2404:8580::/32, + 2404:8700::/32, + 2404:8880::/32, + 2404:8a80::/32, + 2404:8dc0::/32, + 2404:9340::/32, + 2404:9b80::/32, + 2404:9c80::/32, + 2404:a000::/32, + 2404:a080::/32, + 2404:a0c0::/32, + 2404:a180::/32, + 2404:a240::/32, + 2404:a740::/32, + 2404:b100::/32, + 2404:b340::/32, + 2404:b3c0::/32, + 2404:b440::/32, + 2404:b4c0::/32, + 2404:b900::/32, + 2404:bbc0::/32, + 2404:bc40::/32, + 2404:c1c0::/32, + 2404:c240::/32, + 2404:c2c0::/32, + 2404:c300::/32, + 2404:c3c0::/32, + 2404:c440::/32, + 2404:c4c0::/32, + 2404:c540::/32, + 2404:c5c0::/32, + 2404:c940::/32, + 2404:c9c0::/32, + 2404:cd00::/32, + 2404:d040::/32, + 2404:d080::/32, + 2404:d140::/32, + 2404:d280::/32, + 2404:d3c0::/32, + 2404:d640::/32, + 2404:d6c0::/32, + 2404:d7c0::/32, + 2404:d840::/32, + 2404:dd80::/32, + 2404:df00::/32, + 2404:e280::/32, + 2404:e540::/32, + 2404:e780::/32, + 2404:e880::/32, + 2404:e8c0::/32, + 2404:eb80::/32, + 2404:ec40::/32, + 2404:ecc0::/32, + 2404:edc0::/32, + 2404:f040::/32, + 2404:f4c0:f000::/44, + 2404:f7c0::/32, + 2405:80::/32, + 2405:480::/32, + 2405:580::/32, + 2405:680::/32, + 2405:6c0:2::/47, + 2405:6c0:4::/46, + 2405:6c0:8::/45, + 2405:6c0:10::/44, + 2405:6c0:20::/43, + 2405:6c0:40::/42, + 2405:6c0:80::/41, + 2405:6c0:100::/40, + 2405:6c0:200::/39, + 2405:6c0:400::/38, + 2405:6c0:800::/37, + 2405:6c0:1000::/36, + 2405:6c0:2000::/35, + 2405:6c0:4000::/34, + 2405:6c0:8000::/33, + 2405:780::/32, + 2405:880::/32, + 2405:940::/32, + 2405:980::/32, + 2405:9c0::/32, + 2405:a80::/32, + 2405:b80::/32, + 2405:c80::/32, + 2405:d80::/32, + 2405:e80::/32, + 2405:f80::/32, + 2405:1080::/32, + 2405:1180::/32, + 2405:1280::/32, + 2405:1380::/32, + 2405:1480::/32, + 2405:1580::/32, + 2405:1680::/32, + 2405:18c0::/32, + 2405:1c80::/32, + 2405:1d80::/32, + 2405:1e80::/32, + 2405:1f80::/32, + 2405:1fc0::/32, + 2405:2080::/32, + 2405:2180::/32, + 2405:2280::/32, + 2405:2340::/32, + 2405:2380::/32, + 2405:2480::/32, + 2405:24c0::/32, + 2405:2580::/32, + 2405:2680::/32, + 2405:2780::/32, + 2405:2880::/32, + 2405:2980::/32, + 2405:2a80::/32, + 2405:2b80::/32, + 2405:2bc0::/32, + 2405:2c80::/32, + 2405:2d80::/32, + 2405:2e80::/32, + 2405:2ec0::/32, + 2405:2f40::/32, + 2405:2f80::/32, + 2405:3140::/32, + 2405:31c0::/32, + 2405:37c0::/32, + 2405:3880::/32, + 2405:3980::/32, + 2405:39c0::/32, + 2405:3a80::/32, + 2405:3ac0::/32, + 2405:3b00::/32, + 2405:3b80::/32, + 2405:3bc0::/32, + 2405:3c40::/32, + 2405:3c80::/32, + 2405:3d80::/32, + 2405:3e80::/32, + 2405:3f40::/32, + 2405:3f80::/32, + 2405:4080::/32, + 2405:4140::/32, + 2405:4180::/32, + 2405:41c0::/32, + 2405:4280::/32, + 2405:4380::/32, + 2405:4480::/32, + 2405:44c0::/32, + 2405:4540::/32, + 2405:4580::/32, + 2405:4680::/32, + 2405:4780::/32, + 2405:4880::/32, + 2405:4980::/32, + 2405:4a80::/32, + 2405:4b80::/32, + 2405:4d40::/32, + 2405:4e80::/32, + 2405:4f80::/32, + 2405:5080::/32, + 2405:5180::/32, + 2405:5240:400::/38, + 2405:5240:c00::/38, + 2405:5240:1000::/36, + 2405:5240:2000::/35, + 2405:5240:4000::/34, + 2405:5240:8000::/33, + 2405:5280::/32, + 2405:52c0::/32, + 2405:5380::/32, + 2405:5480::/32, + 2405:5580::/32, + 2405:5680::/32, + 2405:5780::/32, + 2405:57c0::/32, + 2405:5880::/32, + 2405:5980::/32, + 2405:5a80::/32, + 2405:5b80::/32, + 2405:5c80::/32, + 2405:5cc0::/32, + 2405:5d40::/32, + 2405:5d80::/32, + 2405:5dc0::/32, + 2405:5e80::/32, + 2405:5f80::/32, + 2405:6080::/32, + 2405:6180::/32, + 2405:6200::/32, + 2405:6880::/32, + 2405:68c0::/32, + 2405:6940::/32, + 2405:69c0::/32, + 2405:6a80::/32, + 2405:6b80::/32, + 2405:6c80::/32, + 2405:6d80::/32, + 2405:6e80::/32, + 2405:6f00::/32, + 2405:6f80::/32, + 2405:7040::/32, + 2405:7080::/32, + 2405:7180::/32, + 2405:7280::/32, + 2405:7380::/32, + 2405:7480::/32, + 2405:7580::/32, + 2405:7680::/32, + 2405:7780::/32, + 2405:7880::/32, + 2405:78c0::/32, + 2405:7980::/32, + 2405:79c0::/32, + 2405:7a80::/32, + 2405:7b80::/32, + 2405:7c80::/32, + 2405:7d40::/32, + 2405:7f40::/32, + 2405:7fc0::/32, + 2405:8280::/32, + 2405:8480::/32, + 2405:84c0::/34, + 2405:84c0:4000::/35, + 2405:84c0:6000::/37, + 2405:84c0:6800::/38, + 2405:84c0:6c00::/39, + 2405:84c0:6e00::/40, + 2405:84c0:7000::/36, + 2405:84c0:8000::/48, + 2405:84c0:8002::/48, + 2405:84c0:8006::/48, + 2405:84c0:800a::/47, + 2405:84c0:800c::/46, + 2405:84c0:8010::/48, + 2405:84c0:8019::/48, + 2405:84c0:801e::/47, + 2405:84c0:8020::/47, + 2405:84c0:8036::/47, + 2405:84c0:8038::/46, + 2405:84c0:803c::/47, + 2405:84c0:803e::/48, + 2405:84c0:8041::/48, + 2405:84c0:8042::/47, + 2405:84c0:8044::/47, + 2405:84c0:8048::/46, + 2405:84c0:804c::/47, + 2405:84c0:804e::/48, + 2405:84c0:8050::/48, + 2405:84c0:8053::/48, + 2405:84c0:8058::/45, + 2405:84c0:8061::/48, + 2405:84c0:8062::/47, + 2405:84c0:8064::/46, + 2405:84c0:8068::/45, + 2405:84c0:8070::/44, + 2405:84c0:8080::/41, + 2405:84c0:8100::/40, + 2405:84c0:8200::/39, + 2405:84c0:8400::/38, + 2405:84c0:8800::/37, + 2405:84c0:9000::/36, + 2405:84c0:a000::/40, + 2405:84c0:a101::/48, + 2405:84c0:a102::/47, + 2405:84c0:a104::/46, + 2405:84c0:a108::/45, + 2405:84c0:a110::/44, + 2405:84c0:a120::/43, + 2405:84c0:a140::/42, + 2405:84c0:a180::/41, + 2405:84c0:a200::/39, + 2405:84c0:a400::/38, + 2405:84c0:a800::/37, + 2405:84c0:b000::/36, + 2405:84c0:c000::/34, + 2405:8580::/32, + 2405:8680::/32, + 2405:8780::/32, + 2405:8880::/32, + 2405:8980::/32, + 2405:8a40::/32, + 2405:8a80::/32, + 2405:8ac0::/32, + 2405:8b80::/32, + 2405:8c80::/32, + 2405:8d80::/32, + 2405:8e80::/32, + 2405:8f80::/32, + 2405:9080::/32, + 2405:9180::/32, + 2405:9280::/32, + 2405:9300::/32, + 2405:9340::/32, + 2405:9380::/32, + 2405:93c0::/32, + 2405:9480::/32, + 2405:94c0::/32, + 2405:9580::/32, + 2405:9680::/32, + 2405:9700::/32, + 2405:9780::/32, + 2405:97c0::/32, + 2405:9880::/32, + 2405:9900::/32, + 2405:9980::/32, + 2405:99c0::/32, + 2405:9a80::/32, + 2405:9b00::/32, + 2405:9b80::/32, + 2405:9bc0::/32, + 2405:9e00::/32, + 2405:a240::/32, + 2405:a500::/32, + 2405:a680::/32, + 2405:a900::/32, + 2405:a980::/32, + 2405:aa80::/32, + 2405:ab00::/32, + 2405:ad00::/32, + 2405:af00::/32, + 2405:b100::/32, + 2405:b300::/32, + 2405:b7c0::/32, + 2405:b880::/32, + 2405:b980::/32, + 2405:bb00::/32, + 2405:bd00::/32, + 2405:bd80::/32, + 2405:bdc0::/32, + 2405:be80::/32, + 2405:bf00::/32, + 2405:c040::/32, + 2405:c280::/32, + 2405:c380::/32, + 2405:c480::/32, + 2405:c500::/32, + 2405:c580::/32, + 2405:c680::/32, + 2405:c780::/32, + 2405:c880::/32, + 2405:c980::/32, + 2405:ca80::/32, + 2405:cb80::/32, + 2405:cc80::/32, + 2405:cd80::/32, + 2405:ce80::/32, + 2405:d280::/32, + 2405:d4c0::/32, + 2405:d700::/32, + 2405:d900::/32, + 2405:df40::/32, + 2405:e000::/32, + 2405:e040::/32, + 2405:e1c0::/32, + 2405:e600::/32, + 2405:ef40::/30, + 2405:f340::/32, + 2405:f6c0::/32, + 2405:f940::/32, + 2405:fdc0::/32, + 2406:40::/32, + 2406:80::/32, + 2406:c0::/32, + 2406:140:400::/38, + 2406:140:c00::/38, + 2406:140:1000::/36, + 2406:140:2000::/35, + 2406:140:4000::/34, + 2406:140:8000::/33, + 2406:280::/32, + 2406:440::/32, + 2406:4c0::/32, + 2406:840::/48, + 2406:840:3::/48, + 2406:840:4::/46, + 2406:840:8::/45, + 2406:840:18::/45, + 2406:840:21::/48, + 2406:840:22::/47, + 2406:840:24::/46, + 2406:840:28::/45, + 2406:840:30::/44, + 2406:840:40::/46, + 2406:840:45::/48, + 2406:840:47::/48, + 2406:840:48::/45, + 2406:840:50::/44, + 2406:840:60::/44, + 2406:840:70::/45, + 2406:840:7a::/47, + 2406:840:7c::/46, + 2406:840:82::/47, + 2406:840:84::/46, + 2406:840:88::/45, + 2406:840:91::/48, + 2406:840:92::/47, + 2406:840:94::/46, + 2406:840:98::/45, + 2406:840:a0::/43, + 2406:840:c0::/42, + 2406:840:102::/48, + 2406:840:104::/46, + 2406:840:108::/45, + 2406:840:111::/48, + 2406:840:112::/47, + 2406:840:114::/46, + 2406:840:118::/45, + 2406:840:120::/43, + 2406:840:140::/42, + 2406:840:181::/48, + 2406:840:182::/47, + 2406:840:184::/46, + 2406:840:188::/45, + 2406:840:190::/44, + 2406:840:1a0::/43, + 2406:840:1c0::/42, + 2406:840:201::/48, + 2406:840:202::/47, + 2406:840:204::/46, + 2406:840:208::/45, + 2406:840:210::/44, + 2406:840:220::/43, + 2406:840:240::/42, + 2406:840:280::/42, + 2406:840:2c0::/43, + 2406:840:2e1::/48, + 2406:840:2e2::/47, + 2406:840:2e4::/46, + 2406:840:2e8::/45, + 2406:840:2f0::/44, + 2406:840:300::/41, + 2406:840:382::/47, + 2406:840:384::/46, + 2406:840:388::/45, + 2406:840:390::/44, + 2406:840:3a0::/43, + 2406:840:3c0::/47, + 2406:840:3c3::/48, + 2406:840:3c4::/46, + 2406:840:3c8::/45, + 2406:840:3d0::/44, + 2406:840:3e1::/48, + 2406:840:3e2::/47, + 2406:840:3e4::/46, + 2406:840:3e8::/45, + 2406:840:3f0::/44, + 2406:840:400::/44, + 2406:840:412::/47, + 2406:840:414::/46, + 2406:840:418::/45, + 2406:840:421::/48, + 2406:840:422::/47, + 2406:840:424::/46, + 2406:840:428::/45, + 2406:840:430::/44, + 2406:840:440::/42, + 2406:840:481::/48, + 2406:840:482::/47, + 2406:840:484::/46, + 2406:840:488::/45, + 2406:840:490::/44, + 2406:840:4a0::/43, + 2406:840:4c0::/42, + 2406:840:500::/42, + 2406:840:541::/48, + 2406:840:542::/47, + 2406:840:544::/46, + 2406:840:548::/45, + 2406:840:550::/44, + 2406:840:560::/43, + 2406:840:580::/41, + 2406:840:600::/41, + 2406:840:681::/48, + 2406:840:682::/47, + 2406:840:684::/46, + 2406:840:688::/45, + 2406:840:690::/44, + 2406:840:6a0::/43, + 2406:840:6c0::/48, + 2406:840:6c2::/47, + 2406:840:6c4::/46, + 2406:840:6c8::/45, + 2406:840:6d0::/44, + 2406:840:6e0::/43, + 2406:840:700::/44, + 2406:840:711::/48, + 2406:840:712::/47, + 2406:840:714::/46, + 2406:840:718::/45, + 2406:840:720::/43, + 2406:840:740::/42, + 2406:840:780::/41, + 2406:840:804::/47, + 2406:840:807::/48, + 2406:840:808::/45, + 2406:840:810::/44, + 2406:840:820::/43, + 2406:840:840::/48, + 2406:840:843::/48, + 2406:840:844::/46, + 2406:840:848::/45, + 2406:840:850::/44, + 2406:840:861::/48, + 2406:840:862::/47, + 2406:840:864::/46, + 2406:840:868::/45, + 2406:840:870::/44, + 2406:840:884::/46, + 2406:840:888::/45, + 2406:840:890::/44, + 2406:840:8a0::/43, + 2406:840:8c0::/42, + 2406:840:900::/40, + 2406:840:a01::/48, + 2406:840:a02::/47, + 2406:840:a04::/46, + 2406:840:a08::/45, + 2406:840:a12::/47, + 2406:840:a14::/46, + 2406:840:a18::/45, + 2406:840:a20::/44, + 2406:840:a30::/48, + 2406:840:a33::/48, + 2406:840:a34::/46, + 2406:840:a38::/45, + 2406:840:a40::/42, + 2406:840:a80::/41, + 2406:840:b00::/40, + 2406:840:c00::/47, + 2406:840:c03::/48, + 2406:840:c04::/46, + 2406:840:c08::/45, + 2406:840:c10::/44, + 2406:840:c20::/43, + 2406:840:c40::/43, + 2406:840:c61::/48, + 2406:840:c62::/47, + 2406:840:c64::/46, + 2406:840:c68::/45, + 2406:840:c70::/44, + 2406:840:c80::/43, + 2406:840:ca0::/44, + 2406:840:cb0::/45, + 2406:840:cb9::/48, + 2406:840:cba::/47, + 2406:840:cbc::/46, + 2406:840:cc0::/47, + 2406:840:cc3::/48, + 2406:840:cc4::/46, + 2406:840:cc8::/45, + 2406:840:cd0::/44, + 2406:840:ce0::/43, + 2406:840:d00::/40, + 2406:840:e00::/39, + 2406:840:1000::/48, + 2406:840:1002::/47, + 2406:840:1004::/46, + 2406:840:1008::/45, + 2406:840:1010::/44, + 2406:840:1020::/43, + 2406:840:1040::/42, + 2406:840:1080::/41, + 2406:840:1100::/40, + 2406:840:1200::/39, + 2406:840:1400::/38, + 2406:840:1801::/48, + 2406:840:1802::/47, + 2406:840:1804::/46, + 2406:840:1808::/45, + 2406:840:1810::/44, + 2406:840:1820::/43, + 2406:840:1840::/42, + 2406:840:1880::/41, + 2406:840:1900::/40, + 2406:840:1a00::/39, + 2406:840:1c00::/38, + 2406:840:2000::/39, + 2406:840:2200::/40, + 2406:840:2300::/41, + 2406:840:2381::/48, + 2406:840:2382::/47, + 2406:840:2384::/46, + 2406:840:2388::/45, + 2406:840:2390::/44, + 2406:840:23a0::/43, + 2406:840:23c0::/42, + 2406:840:2400::/38, + 2406:840:2801::/48, + 2406:840:2802::/47, + 2406:840:2804::/46, + 2406:840:2808::/45, + 2406:840:2810::/44, + 2406:840:2820::/43, + 2406:840:2840::/42, + 2406:840:2880::/48, + 2406:840:2882::/47, + 2406:840:2884::/46, + 2406:840:2888::/45, + 2406:840:2890::/44, + 2406:840:28a0::/43, + 2406:840:28c0::/42, + 2406:840:2900::/40, + 2406:840:2a00::/39, + 2406:840:2c00::/38, + 2406:840:3000::/37, + 2406:840:3801::/48, + 2406:840:3802::/47, + 2406:840:3804::/46, + 2406:840:3808::/45, + 2406:840:3810::/44, + 2406:840:3820::/43, + 2406:840:3840::/42, + 2406:840:3880::/41, + 2406:840:3900::/40, + 2406:840:3a00::/39, + 2406:840:3c00::/38, + 2406:840:4000::/40, + 2406:840:4102::/47, + 2406:840:4104::/46, + 2406:840:4108::/45, + 2406:840:4110::/44, + 2406:840:4120::/43, + 2406:840:4140::/42, + 2406:840:4180::/41, + 2406:840:4200::/40, + 2406:840:4300::/41, + 2406:840:4380::/42, + 2406:840:43c0::/43, + 2406:840:43e2::/47, + 2406:840:43e4::/46, + 2406:840:43e8::/45, + 2406:840:43f0::/44, + 2406:840:4400::/38, + 2406:840:4802::/47, + 2406:840:4804::/46, + 2406:840:4808::/45, + 2406:840:4810::/44, + 2406:840:4820::/43, + 2406:840:4840::/42, + 2406:840:4882::/47, + 2406:840:4884::/46, + 2406:840:4888::/45, + 2406:840:4890::/44, + 2406:840:48a0::/43, + 2406:840:48c0::/42, + 2406:840:4900::/40, + 2406:840:4a00::/39, + 2406:840:4c00::/38, + 2406:840:5000::/40, + 2406:840:5102::/47, + 2406:840:5104::/46, + 2406:840:5108::/45, + 2406:840:5110::/44, + 2406:840:5120::/43, + 2406:840:5140::/42, + 2406:840:5180::/41, + 2406:840:5200::/39, + 2406:840:5400::/38, + 2406:840:5802::/47, + 2406:840:5804::/46, + 2406:840:5808::/45, + 2406:840:5810::/44, + 2406:840:5820::/43, + 2406:840:5840::/43, + 2406:840:5862::/47, + 2406:840:5864::/46, + 2406:840:5868::/45, + 2406:840:5870::/44, + 2406:840:5882::/47, + 2406:840:5884::/46, + 2406:840:5888::/45, + 2406:840:5890::/44, + 2406:840:58a0::/43, + 2406:840:58c0::/42, + 2406:840:5900::/40, + 2406:840:5a00::/39, + 2406:840:5c02::/47, + 2406:840:5c04::/46, + 2406:840:5c08::/45, + 2406:840:5c10::/44, + 2406:840:5c20::/43, + 2406:840:5c40::/42, + 2406:840:5c80::/41, + 2406:840:5d00::/40, + 2406:840:5e00::/39, + 2406:840:6000::/35, + 2406:840:8000::/40, + 2406:840:8200::/39, + 2406:840:8400::/38, + 2406:840:8800::/37, + 2406:840:9010::/44, + 2406:840:9020::/43, + 2406:840:9040::/42, + 2406:840:9080::/41, + 2406:840:9101::/48, + 2406:840:9103::/48, + 2406:840:9104::/46, + 2406:840:9108::/45, + 2406:840:9113::/48, + 2406:840:9114::/46, + 2406:840:9118::/45, + 2406:840:9120::/43, + 2406:840:9140::/43, + 2406:840:9160::/44, + 2406:840:9170::/45, + 2406:840:9179::/48, + 2406:840:917a::/47, + 2406:840:917c::/46, + 2406:840:9180::/41, + 2406:840:9300::/40, + 2406:840:9400::/39, + 2406:840:9610::/44, + 2406:840:9630::/44, + 2406:840:9640::/43, + 2406:840:9660::/48, + 2406:840:9662::/47, + 2406:840:9664::/47, + 2406:840:9667::/48, + 2406:840:9668::/45, + 2406:840:9670::/44, + 2406:840:9690::/44, + 2406:840:96a0::/43, + 2406:840:96c0::/42, + 2406:840:9800::/46, + 2406:840:9808::/45, + 2406:840:9810::/45, + 2406:840:9818::/47, + 2406:840:981b::/48, + 2406:840:9820::/43, + 2406:840:9850::/44, + 2406:840:9860::/47, + 2406:840:9865::/48, + 2406:840:9866::/47, + 2406:840:9868::/45, + 2406:840:9870::/44, + 2406:840:9880::/41, + 2406:840:9900::/42, + 2406:840:9940::/43, + 2406:840:9960::/47, + 2406:840:9963::/48, + 2406:840:9965::/48, + 2406:840:9968::/46, + 2406:840:996d::/48, + 2406:840:996e::/47, + 2406:840:9970::/46, + 2406:840:9974::/47, + 2406:840:9976::/48, + 2406:840:9978::/45, + 2406:840:9981::/48, + 2406:840:9982::/47, + 2406:840:9984::/46, + 2406:840:9988::/45, + 2406:840:99a0::/43, + 2406:840:99c0::/42, + 2406:840:9a00::/39, + 2406:840:9c00::/39, + 2406:840:9e00::/41, + 2406:840:9e80::/42, + 2406:840:9ec0::/43, + 2406:840:9ee0::/44, + 2406:840:9f00::/41, + 2406:840:9f80::/42, + 2406:840:9fc0::/43, + 2406:840:9fe0::/44, + 2406:840:a000::/35, + 2406:840:c000::/37, + 2406:840:c800::/38, + 2406:840:cd00::/40, + 2406:840:ce00::/39, + 2406:840:e000::/41, + 2406:840:e090::/44, + 2406:840:e0a0::/43, + 2406:840:e0c0::/45, + 2406:840:e0c8::/46, + 2406:840:e0cc::/47, + 2406:840:e0ce::/48, + 2406:840:e0d0::/44, + 2406:840:e0e4::/48, + 2406:840:e0e6::/47, + 2406:840:e0e9::/48, + 2406:840:e0ea::/47, + 2406:840:e0ec::/46, + 2406:840:e0f0::/44, + 2406:840:e100::/45, + 2406:840:e108::/46, + 2406:840:e10c::/47, + 2406:840:e10e::/48, + 2406:840:e110::/44, + 2406:840:e120::/43, + 2406:840:e140::/45, + 2406:840:e148::/46, + 2406:840:e14c::/47, + 2406:840:e14e::/48, + 2406:840:e150::/44, + 2406:840:e160::/43, + 2406:840:e190::/44, + 2406:840:e1a0::/43, + 2406:840:e1c0::/42, + 2406:840:e210::/44, + 2406:840:e220::/43, + 2406:840:e240::/43, + 2406:840:e260::/44, + 2406:840:e271::/48, + 2406:840:e272::/47, + 2406:840:e274::/46, + 2406:840:e278::/46, + 2406:840:e27c::/47, + 2406:840:e27e::/48, + 2406:840:e290::/44, + 2406:840:e2a0::/43, + 2406:840:e2c0::/42, + 2406:840:e300::/48, + 2406:840:e305::/48, + 2406:840:e307::/48, + 2406:840:e308::/45, + 2406:840:e310::/44, + 2406:840:e320::/44, + 2406:840:e340::/44, + 2406:840:e350::/48, + 2406:840:e352::/47, + 2406:840:e355::/48, + 2406:840:e356::/47, + 2406:840:e358::/46, + 2406:840:e35c::/48, + 2406:840:e35e::/47, + 2406:840:e360::/43, + 2406:840:e380::/41, + 2406:840:e400::/40, + 2406:840:e500::/42, + 2406:840:e540::/43, + 2406:840:e560::/44, + 2406:840:e570::/45, + 2406:840:e578::/46, + 2406:840:e57c::/47, + 2406:840:e57f::/48, + 2406:840:e580::/41, + 2406:840:e610::/44, + 2406:840:e622::/47, + 2406:840:e624::/46, + 2406:840:e628::/45, + 2406:840:e630::/44, + 2406:840:e640::/43, + 2406:840:e660::/46, + 2406:840:e664::/47, + 2406:840:e668::/45, + 2406:840:e670::/44, + 2406:840:e680::/41, + 2406:840:e700::/43, + 2406:840:e730::/44, + 2406:840:e740::/42, + 2406:840:e780::/41, + 2406:840:e800::/45, + 2406:840:e808::/46, + 2406:840:e80c::/47, + 2406:840:e80e::/48, + 2406:840:e810::/44, + 2406:840:e820::/43, + 2406:840:e840::/45, + 2406:840:e848::/46, + 2406:840:e84c::/47, + 2406:840:e84e::/48, + 2406:840:e850::/44, + 2406:840:e860::/43, + 2406:840:e880::/41, + 2406:840:e900::/40, + 2406:840:ea00::/40, + 2406:840:eb06::/48, + 2406:840:eb09::/48, + 2406:840:eb0a::/47, + 2406:840:eb0c::/47, + 2406:840:eb0e::/48, + 2406:840:eb10::/44, + 2406:840:eb20::/43, + 2406:840:eb40::/42, + 2406:840:ebc0::/42, + 2406:840:ec00::/41, + 2406:840:ec80::/42, + 2406:840:ecc0::/43, + 2406:840:ecf0::/44, + 2406:840:ed00::/40, + 2406:840:ee00::/42, + 2406:840:ee42::/47, + 2406:840:ee45::/48, + 2406:840:ee46::/47, + 2406:840:ee48::/47, + 2406:840:ee4a::/48, + 2406:840:ee4c::/48, + 2406:840:ee4e::/47, + 2406:840:ee50::/44, + 2406:840:ee60::/43, + 2406:840:ee80::/43, + 2406:840:eea0::/44, + 2406:840:eeb0::/48, + 2406:840:eeb3::/48, + 2406:840:eeb4::/46, + 2406:840:eeb8::/45, + 2406:840:eec0::/43, + 2406:840:eee0::/46, + 2406:840:eee4::/48, + 2406:840:eee6::/47, + 2406:840:eee8::/45, + 2406:840:eef0::/44, + 2406:840:ef00::/41, + 2406:840:ef80::/42, + 2406:840:efc0::/43, + 2406:840:efe0::/44, + 2406:840:f000::/41, + 2406:840:f080::/43, + 2406:840:f0a0::/48, + 2406:840:f0a2::/47, + 2406:840:f0a4::/46, + 2406:840:f0a8::/47, + 2406:840:f0ab::/48, + 2406:840:f0ac::/46, + 2406:840:f0b0::/44, + 2406:840:f0c0::/42, + 2406:840:f101::/48, + 2406:840:f102::/47, + 2406:840:f104::/46, + 2406:840:f108::/45, + 2406:840:f110::/44, + 2406:840:f120::/43, + 2406:840:f140::/42, + 2406:840:f180::/41, + 2406:840:f200::/40, + 2406:840:f300::/43, + 2406:840:f320::/44, + 2406:840:f330::/46, + 2406:840:f334::/47, + 2406:840:f336::/48, + 2406:840:f338::/45, + 2406:840:f340::/42, + 2406:840:f390::/44, + 2406:840:f3a0::/43, + 2406:840:f3c0::/42, + 2406:840:f400::/48, + 2406:840:f403::/48, + 2406:840:f404::/46, + 2406:840:f408::/45, + 2406:840:f410::/44, + 2406:840:f420::/43, + 2406:840:f440::/45, + 2406:840:f448::/46, + 2406:840:f44c::/47, + 2406:840:f44e::/48, + 2406:840:f450::/44, + 2406:840:f460::/43, + 2406:840:f480::/41, + 2406:840:f500::/40, + 2406:840:f600::/42, + 2406:840:f640::/43, + 2406:840:f670::/44, + 2406:840:f680::/47, + 2406:840:f682::/48, + 2406:840:f684::/46, + 2406:840:f688::/45, + 2406:840:f690::/44, + 2406:840:f6a0::/43, + 2406:840:f6c0::/42, + 2406:840:f700::/40, + 2406:840:f800::/42, + 2406:840:f840::/43, + 2406:840:f864::/46, + 2406:840:f86a::/47, + 2406:840:f86d::/48, + 2406:840:f86e::/47, + 2406:840:f870::/44, + 2406:840:f880::/45, + 2406:840:f888::/48, + 2406:840:f88a::/47, + 2406:840:f88c::/46, + 2406:840:f890::/44, + 2406:840:f8a0::/43, + 2406:840:f8c0::/42, + 2406:840:f900::/41, + 2406:840:f980::/44, + 2406:840:f9a0::/43, + 2406:840:f9c0::/42, + 2406:840:fa00::/48, + 2406:840:fa02::/47, + 2406:840:fa04::/46, + 2406:840:fa08::/45, + 2406:840:fa10::/44, + 2406:840:fa20::/43, + 2406:840:fa41::/48, + 2406:840:fa42::/47, + 2406:840:fa44::/46, + 2406:840:fa48::/45, + 2406:840:fa50::/44, + 2406:840:fa70::/44, + 2406:840:fa81::/48, + 2406:840:fa82::/47, + 2406:840:fa84::/46, + 2406:840:fa88::/45, + 2406:840:fa90::/44, + 2406:840:faa0::/43, + 2406:840:fac0::/42, + 2406:840:fb00::/40, + 2406:840:fc14::/46, + 2406:840:fc18::/45, + 2406:840:fc24::/46, + 2406:840:fc28::/45, + 2406:840:fc34::/46, + 2406:840:fc38::/45, + 2406:840:fc44::/46, + 2406:840:fc48::/45, + 2406:840:fc54::/46, + 2406:840:fc58::/45, + 2406:840:fc64::/46, + 2406:840:fc68::/45, + 2406:840:fc74::/46, + 2406:840:fc78::/45, + 2406:840:fc80::/44, + 2406:840:fc94::/46, + 2406:840:fc98::/45, + 2406:840:fca4::/46, + 2406:840:fca8::/45, + 2406:840:fcb4::/46, + 2406:840:fcb8::/45, + 2406:840:fcc0::/44, + 2406:840:fcd1::/48, + 2406:840:fcd2::/47, + 2406:840:fcd4::/46, + 2406:840:fcd8::/45, + 2406:840:fce0::/44, + 2406:840:fcf6::/47, + 2406:840:fcf8::/45, + 2406:840:fd00::/43, + 2406:840:fd20::/44, + 2406:840:fd31::/48, + 2406:840:fd32::/47, + 2406:840:fd34::/46, + 2406:840:fd38::/45, + 2406:840:fd41::/48, + 2406:840:fd42::/47, + 2406:840:fd44::/48, + 2406:840:fd47::/48, + 2406:840:fd4a::/47, + 2406:840:fd4c::/46, + 2406:840:fd51::/48, + 2406:840:fd52::/47, + 2406:840:fd54::/48, + 2406:840:fd57::/48, + 2406:840:fd5a::/47, + 2406:840:fd5c::/46, + 2406:840:fd61::/48, + 2406:840:fd62::/47, + 2406:840:fd64::/48, + 2406:840:fd67::/48, + 2406:840:fd6a::/47, + 2406:840:fd6c::/46, + 2406:840:fd71::/48, + 2406:840:fd72::/47, + 2406:840:fd74::/48, + 2406:840:fd77::/48, + 2406:840:fd7a::/47, + 2406:840:fd7c::/46, + 2406:840:fd81::/48, + 2406:840:fd82::/47, + 2406:840:fd84::/48, + 2406:840:fd87::/48, + 2406:840:fd8a::/47, + 2406:840:fd8c::/46, + 2406:840:fd94::/46, + 2406:840:fd98::/46, + 2406:840:fd9c::/47, + 2406:840:fd9e::/48, + 2406:840:fda1::/48, + 2406:840:fda2::/47, + 2406:840:fda4::/46, + 2406:840:fda8::/45, + 2406:840:fdb1::/48, + 2406:840:fdb2::/47, + 2406:840:fdb4::/46, + 2406:840:fdb8::/45, + 2406:840:fdc1::/48, + 2406:840:fdc2::/47, + 2406:840:fdc4::/46, + 2406:840:fdc8::/45, + 2406:840:fdd0::/44, + 2406:840:fde0::/43, + 2406:840:fe00::/43, + 2406:840:fe20::/46, + 2406:840:fe24::/47, + 2406:840:fe26::/48, + 2406:840:fe28::/45, + 2406:840:fe30::/44, + 2406:840:fe40::/44, + 2406:840:fe52::/47, + 2406:840:fe54::/46, + 2406:840:fe58::/45, + 2406:840:fe60::/43, + 2406:840:fe80::/44, + 2406:840:fe95::/48, + 2406:840:fe9c::/48, + 2406:840:fe9f::/48, + 2406:840:fea0::/48, + 2406:840:fea9::/48, + 2406:840:feb0::/47, + 2406:840:feb2::/48, + 2406:840:feb4::/46, + 2406:840:feb8::/45, + 2406:840:fec1::/48, + 2406:840:fec6::/47, + 2406:840:fec8::/47, + 2406:840:fed0::/48, + 2406:840:fed2::/48, + 2406:840:fed5::/48, + 2406:840:fed6::/48, + 2406:840:fed9::/48, + 2406:840:feda::/48, + 2406:840:fede::/48, + 2406:840:fee0::/45, + 2406:840:fee8::/46, + 2406:840:feec::/48, + 2406:840:feee::/47, + 2406:840:feff::/48, + 2406:840:ff00::/40, + 2406:880::/32, + 2406:8c0::/32, + 2406:d80::/32, + 2406:e80::/32, + 2406:f80::/32, + 2406:1080::/32, + 2406:1100::/32, + 2406:1180::/32, + 2406:1280::/32, + 2406:1380::/32, + 2406:1480::/32, + 2406:1580::/32, + 2406:15c0::/32, + 2406:1680::/32, + 2406:1780::/32, + 2406:1880::/32, + 2406:1980::/32, + 2406:1a80::/32, + 2406:1b80::/32, + 2406:1c80::/32, + 2406:1d80::/32, + 2406:1e40::/32, + 2406:1e80::/32, + 2406:1f80::/32, + 2406:2080::/32, + 2406:2640::/32, + 2406:2700::/32, + 2406:2780::/32, + 2406:2880::/32, + 2406:2980::/32, + 2406:2a80::/32, + 2406:2b80::/32, + 2406:2c40::/32, + 2406:2c80::/32, + 2406:2d80::/32, + 2406:2e80::/32, + 2406:2f80::/32, + 2406:3080::/32, + 2406:3180::/32, + 2406:31c0::/32, + 2406:3280::/32, + 2406:3300::/32, + 2406:3340::/32, + 2406:3380::/32, + 2406:3480::/32, + 2406:34c0::/32, + 2406:3580::/32, + 2406:3640::/32, + 2406:3680::/32, + 2406:3700::/32, + 2406:3780::/32, + 2406:3880::/32, + 2406:3980::/32, + 2406:39c0::/32, + 2406:3ac0::/32, + 2406:3e80::/32, + 2406:3f80::/32, + 2406:4080::/32, + 2406:40c0::/32, + 2406:4180::/32, + 2406:4280::/32, + 2406:42c0::/32, + 2406:4340::/32, + 2406:4380::/32, + 2406:43c0::/32, + 2406:4480::/32, + 2406:4500::/32, + 2406:4680::/32, + 2406:4b80::/32, + 2406:4c80::/32, + 2406:4d00::/32, + 2406:4d80::/32, + 2406:4e80::/32, + 2406:4f00::/32, + 2406:4f80::/32, + 2406:5080::/32, + 2406:50c0::/32, + 2406:5180::/32, + 2406:5280::/32, + 2406:52c0::/32, + 2406:5340::/32, + 2406:5380::/32, + 2406:5480::/32, + 2406:5580::/32, + 2406:5680::/32, + 2406:5780::/32, + 2406:5840::/31, + 2406:5940::/32, + 2406:5980::/32, + 2406:5a40::/32, + 2406:5ac0::/32, + 2406:5b40::/32, + 2406:5d80::/32, + 2406:5e80::/32, + 2406:5f80::/32, + 2406:6080::/32, + 2406:6100::/32, + 2406:6180::/32, + 2406:61c0::/29, + 2406:6280::/32, + 2406:6300::/32, + 2406:6340::/32, + 2406:6380::/32, + 2406:6480::/32, + 2406:6500::/32, + 2406:6580::/32, + 2406:65c0::/32, + 2406:6640::/32, + 2406:6680::/32, + 2406:6780::/32, + 2406:6880::/32, + 2406:6980::/32, + 2406:6a80::/32, + 2406:6b80::/32, + 2406:6bc0::/32, + 2406:6c80::/32, + 2406:6d80::/32, + 2406:6e80::/32, + 2406:6f80::/32, + 2406:7080::/32, + 2406:7280::/32, + 2406:7380::/32, + 2406:7480::/32, + 2406:7580::/32, + 2406:7680::/32, + 2406:7780::/32, + 2406:7880::/32, + 2406:7980::/32, + 2406:7a80::/32, + 2406:7b80::/32, + 2406:7c80::/32, + 2406:7d00::/32, + 2406:7d80::/32, + 2406:7e80::/32, + 2406:7f80::/32, + 2406:7fc0::/32, + 2406:8080::/32, + 2406:8180::/32, + 2406:8280::/32, + 2406:8380::/32, + 2406:8480::/32, + 2406:8500::/32, + 2406:8580::/32, + 2406:8780::/32, + 2406:8880::/32, + 2406:8980::/32, + 2406:8a80::/32, + 2406:8b80::/32, + 2406:8c80::/32, + 2406:8d80::/32, + 2406:8e80::/32, + 2406:8f40::/32, + 2406:8f80::/32, + 2406:9200::/32, + 2406:9380::/32, + 2406:9480::/32, + 2406:94c0::/32, + 2406:9780::/32, + 2406:9d80::/32, + 2406:9e80::/32, + 2406:9f80::/32, + 2406:a080::/32, + 2406:a180::/32, + 2406:a280::/32, + 2406:a380::/32, + 2406:a480::/32, + 2406:a580::/32, + 2406:a680::/32, + 2406:a780::/32, + 2406:a7c0::/32, + 2406:a880::/32, + 2406:a8c0::/32, + 2406:a980::/32, + 2406:aa80::/32, + 2406:aac0::/32, + 2406:ab80::/32, + 2406:ac80::/32, + 2406:acc0::/32, + 2406:ad40::/32, + 2406:ad80::/32, + 2406:ae80::/32, + 2406:af80::/32, + 2406:b080::/32, + 2406:b640::/32, + 2406:b880::/32, + 2406:b980::/32, + 2406:ba80::/32, + 2406:bb80::/32, + 2406:bc80::/32, + 2406:bd40::/32, + 2406:bd80::/32, + 2406:bdc0::/32, + 2406:be80::/32, + 2406:bf80::/32, + 2406:c080::/32, + 2406:c180::/32, + 2406:c280::/32, + 2406:c340::/32, + 2406:c480::/32, + 2406:c580::/32, + 2406:c680::/32, + 2406:c780::/32, + 2406:c880::/32, + 2406:c900::/32, + 2406:c980::/32, + 2406:ca80::/32, + 2406:cac0::/32, + 2406:cb80::/32, + 2406:cc80::/32, + 2406:cd80::/32, + 2406:ce80::/32, + 2406:cf00::/30, + 2406:cf80::/32, + 2406:d080::/32, + 2406:d140::/33, + 2406:d140:c000::/34, + 2406:d180::/32, + 2406:d280::/32, + 2406:d2c0::/32, + 2406:d380::/32, + 2406:d440::/32, + 2406:d480::/32, + 2406:d580::/32, + 2406:d680::/32, + 2406:d780::/32, + 2406:d880::/32, + 2406:d980::/32, + 2406:db80::/32, + 2406:dc80::/32, + 2406:dd00::/32, + 2406:dd80:400::/38, + 2406:dd80:c00::/38, + 2406:dd80:1000::/36, + 2406:dd80:2000::/35, + 2406:dd80:4000::/34, + 2406:dd80:8000::/33, + 2406:de80::/32, + 2406:df80::/32, + 2406:e080::/32, + 2406:e180::/32, + 2406:e2c0::/32, + 2406:e380::/32, + 2406:e3c0::/32, + 2406:e500::/32, + 2406:e580::/32, + 2406:e680::/32, + 2406:e780::/32, + 2406:e8c0::/32, + 2406:ea40::/28, + 2406:f280::/32, + 2406:f300::/32, + 2406:f4c0::/32, + 2406:f7c0::/32, + 2406:f980::/32, + 2406:fc80::/32, + 2406:fd80::/32, + 2406:fe80::/32, + 2406:ff00::/32, + 2407:480::/32, + 2407:580::/32, + 2407:cc0::/32, + 2407:f40::/32, + 2407:17c0::/32, + 2407:1900::/32, + 2407:1d00::/32, + 2407:2280::/32, + 2407:2380::/32, + 2407:23c0::/32, + 2407:2780::/32, + 2407:2840::/32, + 2407:2ac0::/32, + 2407:31c0::/32, + 2407:3340::/32, + 2407:3540::/32, + 2407:3700::/32, + 2407:3740::/32, + 2407:37c0::/32, + 2407:3900::/32, + 2407:3f40::/32, + 2407:43c0::/32, + 2407:4440::/32, + 2407:4580::/32, + 2407:4680::/32, + 2407:4740::/32, + 2407:4880::/32, + 2407:4980::/32, + 2407:4a80::/32, + 2407:4c80::/32, + 2407:4d80::/32, + 2407:4e80::/32, + 2407:4f00::/32, + 2407:5380::/32, + 2407:53c0::/32, + 2407:5500::/32, + 2407:5780::/32, + 2407:5840::/32, + 2407:6040::/32, + 2407:6580::/32, + 2407:6c40::/32, + 2407:7680::/32, + 2407:7780::/32, + 2407:7880::/32, + 2407:7980::/32, + 2407:7c80::/32, + 2407:7d00::/32, + 2407:7d80::/32, + 2407:7e80::/32, + 2407:8880::/32, + 2407:8b80::/32, + 2407:8f40::/32, + 2407:9080::/32, + 2407:9180::/32, + 2407:94c0::/32, + 2407:9680::/32, + 2407:9980::/32, + 2407:9b40::/32, + 2407:9bc0::/32, + 2407:9f00::/32, + 2407:9f80::/32, + 2407:a040::/32, + 2407:a640::/32, + 2407:a7c0::/32, + 2407:a880::/32, + 2407:a940::/32, + 2407:ad80::/32, + 2407:ae80::/32, + 2407:af80::/32, + 2407:b080::/32, + 2407:b180::/32, + 2407:b280::/32, + 2407:b380::/32, + 2407:b580::/32, + 2407:b680::/32, + 2407:b780::/32, + 2407:b880::/32, + 2407:b980::/32, + 2407:ba00::/32, + 2407:ba80::/32, + 2407:bb80::/32, + 2407:bc00::/32, + 2407:bc80::/32, + 2407:bd80::/32, + 2407:bdc0::/32, + 2407:be80::/32, + 2407:bf80::/32, + 2407:c080::/32, + 2407:c380::/32, + 2407:c400::/32, + 2407:c480::/32, + 2407:c580::/32, + 2407:c680::/32, + 2407:c780::/32, + 2407:c880::/32, + 2407:c900::/32, + 2407:c980::/32, + 2407:cb80::/32, + 2407:cc80::/32, + 2407:cd80::/32, + 2407:ce80::/32, + 2407:cf80::/32, + 2407:d480::/32, + 2407:d580::/32, + 2407:d680::/32, + 2407:d780::/32, + 2407:d7c0::/32, + 2407:d880::/32, + 2407:d8c0::/32, + 2407:d980::/32, + 2407:d9c0::/32, + 2407:da80::/32, + 2407:db80::/32, + 2407:dc80::/32, + 2407:dd80::/32, + 2407:de80::/32, + 2407:df80::/32, + 2407:dfc0::/32, + 2407:e080::/32, + 2407:e180::/32, + 2407:e280::/32, + 2407:e380::/32, + 2407:e480::/32, + 2407:e580::/32, + 2407:e680::/32, + 2407:e780::/32, + 2407:e800::/32, + 2407:ea80::/32, + 2407:eb80::/32, + 2407:ec40::/32, + 2407:ec80::/32, + 2407:ecc0::/32, + 2407:ed80::/32, + 2407:ee80::/32, + 2407:ef80::/32, + 2407:f080::/32, + 2407:f180::/32, + 2407:f280::/32, + 2407:f380::/32, + 2407:f480::/32, + 2407:f580::/32, + 2407:f680::/32, + 2407:f780::/32, + 2407:f880::/32, + 2407:f980::/32, + 2407:fa80::/32, + 2407:fb80::/32, + 2407:fc80::/32, + 2407:fd80::/32, + 2408:4000::/28, + 2408:4010::/30, + 2408:4014::/31, + 2408:4016:1::/48, + 2408:4016:2::/47, + 2408:4016:4::/46, + 2408:4016:8::/45, + 2408:4016:10::/44, + 2408:4016:20::/43, + 2408:4016:40::/42, + 2408:4016:80::/41, + 2408:4016:100::/40, + 2408:4016:200::/39, + 2408:4016:400::/38, + 2408:4016:800::/37, + 2408:4016:1000::/36, + 2408:4016:2000::/35, + 2408:4016:4000::/34, + 2408:4016:8000::/33, + 2408:4017::/32, + 2408:4018::/29, + 2408:4020::/27, + 2408:4040::/26, + 2408:4080::/25, + 2408:4100::/24, + 2408:4200::/23, + 2408:6000::/24, + 2408:8000::/20, + 2409:1000::/20, + 2409:2000::/21, + 2409:6000::/20, + 2409:8000::/21, + 2409:8800::/22, + 2409:8c00::/25, + 2409:8c80::/30, + 2409:8c84::/32, + 2409:8c85::/34, + 2409:8c85:4000::/36, + 2409:8c85:5000::/38, + 2409:8c85:5400::/43, + 2409:8c85:5420::/44, + 2409:8c85:5430::/47, + 2409:8c85:5433::/48, + 2409:8c85:5434::/46, + 2409:8c85:5438::/45, + 2409:8c85:5440::/42, + 2409:8c85:5480::/41, + 2409:8c85:5500::/40, + 2409:8c85:5600::/39, + 2409:8c85:5800::/37, + 2409:8c85:6000::/35, + 2409:8c85:8000::/33, + 2409:8c86::/31, + 2409:8c88::/29, + 2409:8c90::/28, + 2409:8ca0::/27, + 2409:8cc0::/26, + 2409:8d00::/24, + 2409:8e00::/23, + 240a:2000::/24, + 240a:4000::/21, + 240a:6000::/24, + 240a:8000::/21, + 240a:a000::/20, + 240a:c000::/20, + 240b:2000::/22, + 240b:4000::/31, + 240b:4002::/36, + 240b:4002:1000::/44, + 240b:4002:1011::/48, + 240b:4002:1012::/47, + 240b:4002:1014::/46, + 240b:4002:1018::/45, + 240b:4002:1020::/43, + 240b:4002:1040::/42, + 240b:4002:1080::/41, + 240b:4002:1100::/40, + 240b:4002:1200::/39, + 240b:4002:1400::/38, + 240b:4002:1800::/37, + 240b:4002:2000::/35, + 240b:4002:4000::/34, + 240b:4002:8000::/33, + 240b:4004::/31, + 240b:4006::/48, + 240b:4006:1000::/43, + 240b:4006:1020::/44, + 240b:4006:1100::/44, + 240b:4006:1110::/45, + 240b:4006:1118::/46, + 240b:4007::/32, + 240b:4009::/32, + 240b:400b::/32, + 240b:400c::/30, + 240b:4010:fffe::/47, + 240b:4011::/32, + 240b:4012::/31, + 240b:4014::/30, + 240b:4018::/30, + 240b:401f::/32, + 240b:6000::/20, + 240b:8000::/21, + 240b:a000::/25, + 240b:e000::/26, + 240c:0:1::/48, + 240c:0:2::/47, + 240c:0:4::/46, + 240c:0:8::/45, + 240c:0:10::/44, + 240c:0:20::/43, + 240c:0:40::/42, + 240c:0:80::/41, + 240c:0:100::/40, + 240c:0:200::/39, + 240c:0:400::/38, + 240c:0:800::/37, + 240c:0:1000::/36, + 240c:0:2000::/35, + 240c:0:4000::/34, + 240c:0:8000::/33, + 240c:6:2::/48, + 240c:6:4::/46, + 240c:6:8::/45, + 240c:6:10::/44, + 240c:6:20::/43, + 240c:6:40::/42, + 240c:6:80::/41, + 240c:6:100::/40, + 240c:6:200::/39, + 240c:6:400::/38, + 240c:6:800::/37, + 240c:6:1000::/36, + 240c:6:2000::/35, + 240c:6:4000::/34, + 240c:6:8000::/33, + 240c:f::/48, + 240c:f:2::/47, + 240c:f:4::/46, + 240c:f:8::/45, + 240c:f:10::/44, + 240c:f:20::/43, + 240c:f:40::/42, + 240c:f:80::/41, + 240c:f:100::/40, + 240c:f:200::/39, + 240c:f:400::/38, + 240c:f:800::/37, + 240c:f:1000::/36, + 240c:f:2000::/35, + 240c:f:4000::/34, + 240c:f:8000::/33, + 240c:4000::/22, + 240c:8000::/21, + 240c:c000::/20, + 240d:4000::/21, + 240d:8000::/24, + 240e::/21, + 240e:800::/24, + 240e:900::/26, + 240e:940::/27, + 240e:960::/28, + 240e:970::/29, + 240e:978::/30, + 240e:97c::/32, + 240e:97d::/33, + 240e:97d:8001::/48, + 240e:97d:8002::/47, + 240e:97d:8004::/46, + 240e:97d:8008::/45, + 240e:97d:8010::/44, + 240e:97d:8020::/43, + 240e:97d:8040::/42, + 240e:97d:8080::/41, + 240e:97d:8100::/40, + 240e:97d:8200::/39, + 240e:97d:8400::/38, + 240e:97d:8800::/37, + 240e:97d:9000::/36, + 240e:97d:a000::/35, + 240e:97d:c000::/34, + 240e:97e::/31, + 240e:980::/25, + 240e:a00::/23, + 240e:c00::/22, + 240e:1000::/20, + 240e:2000::/19, + 240f:4000::/24, + 240f:8000::/24, + 240f:c000::/24, + 2602:f70d::/36, + 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, + 2a03:f900::/29, + 2a04:3e00::/48, + 2a04:3e00:2::/47, + 2a04:3e00:4::/46, + 2a04:3e00:8::/45, + 2a04:3e00:10::/44, + 2a04:3e00:20::/43, + 2a04:3e00:40::/42, + 2a04:3e00:80::/41, + 2a04:3e00:100::/40, + 2a04:3e00:200::/39, + 2a04:3e00:400::/38, + 2a04:3e00:800::/37, + 2a04:3e00:1001::/48, + 2a04:3e00:1002::/47, + 2a04:3e00:1004::/46, + 2a04:3e00:1008::/45, + 2a04:3e00:1010::/44, + 2a04:3e00:1020::/43, + 2a04:3e00:1040::/42, + 2a04:3e00:1080::/41, + 2a04:3e00:1100::/40, + 2a04:3e00:1200::/39, + 2a04:3e00:1400::/38, + 2a04:3e00:1800::/37, + 2a04:3e00:2000::/35, + 2a04:3e00:4000::/34, + 2a04:3e00:8000::/33, + 2a04:3e01::/32, + 2a04:3e02::/31, + 2a04:3e04::/30, + 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:9040::/48, + 2a04:f580:9050::/48, + 2a04:f580:9060::/48, + 2a04:f580:9070::/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:1085::/32, + 2a0a:2840::/29, + 2a0a:d680::/37, + 2a0a:d680:800::/39, + 2a0a:d680:a00::/43, + 2a0a:d680:a20::/44, + 2a0a:d680:a30::/48, + 2a0a:d680:a32::/47, + 2a0a:d680:a34::/46, + 2a0a:d680:a38::/45, + 2a0a:d680:a40::/42, + 2a0a:d680:a80::/41, + 2a0a:d680:b00::/40, + 2a0a:d680:c00::/38, + 2a0a:d680:1000::/36, + 2a0a:d680:2000::/35, + 2a0a:d680:4000::/34, + 2a0a:d680:8000::/40, + 2a0a:d680:8102::/47, + 2a0a:d680:8104::/46, + 2a0a:d680:8108::/45, + 2a0a:d680:8110::/44, + 2a0a:d680:8120::/43, + 2a0a:d680:8140::/42, + 2a0a:d680:8180::/41, + 2a0a:d680:8200::/39, + 2a0a:d680:8400::/38, + 2a0a:d680:8800::/37, + 2a0a:d680:9000::/36, + 2a0a:d680:a000::/35, + 2a0a:d680:c000::/34, + 2a0a:d681::/33, + 2a0a:d681:8000::/34, + 2a0a:d681:c000::/35, + 2a0a:d681:e100::/40, + 2a0a:d681:e200::/39, + 2a0a:d681:e400::/38, + 2a0a:d681:e800::/37, + 2a0a:d681:f000::/36, + 2a0a:d682::/33, + 2a0a:d682:8000::/34, + 2a0a:d682:c000::/36, + 2a0a:d683::/32, + 2a0a:d684::/32, + 2a0a:d685::/40, + 2a0a:d685:100::/41, + 2a0a:d685:180::/42, + 2a0a:d685:1c0::/44, + 2a0a:d685:1d1::/48, + 2a0a:d685:1d2::/47, + 2a0a:d685:1d4::/46, + 2a0a:d685:1d8::/45, + 2a0a:d685:1e2::/47, + 2a0a:d685:1e4::/46, + 2a0a:d685:1e8::/45, + 2a0a:d685:1f0::/45, + 2a0a:d685:1f8::/47, + 2a0a:d685:1fa::/48, + 2a0a:d685:200::/48, + 2a0a:d685:202::/47, + 2a0a:d685:204::/46, + 2a0a:d685:208::/45, + 2a0a:d685:210::/44, + 2a0a:d685:220::/43, + 2a0a:d685:240::/42, + 2a0a:d685:280::/41, + 2a0a:d685:400::/38, + 2a0a:d685:800::/37, + 2a0a:d685:1000::/36, + 2a0a:d685:2000::/35, + 2a0a:d685:4000::/34, + 2a0a:d685:8000::/33, + 2a0a:d686::/32, + 2a0a:d687::/33, + 2a0a:d687:8000::/34, + 2a0a:d687:c000::/35, + 2a0a:d687:e000::/36, + 2a0a:d687:f000::/48, + 2a0a:d687:f002::/47, + 2a0a:d687:f006::/48, + 2a0a:d687:f009::/48, + 2a0a:d687:f00a::/47, + 2a0a:d687:f00c::/46, + 2a0a:d687:f010::/44, + 2a0a:d687:f020::/43, + 2a0a:d687:f040::/42, + 2a0a:d687:f080::/41, + 2a0a:d687:f100::/40, + 2a0a:d687:f200::/39, + 2a0a:d687:f400::/38, + 2a0a:d687:f800::/37, + 2a0c:b641:573::/48, + 2a0e:7580::/32, + 2a0e:7582::/31, + 2a0e:7584::/30, + 2a0e:aa06:49f::/48, + 2a0e:b107:740::/44, + 2a13:1800:1::/48, + 2a13:1800:2::/47, + 2a13:1800:4::/46, + 2a13:1800:8::/45, + 2a13:1800:11::/48, + 2a13:1800:12::/47, + 2a13:1800:14::/46, + 2a13:1800:18::/45, + 2a13:1800:20::/43, + 2a13:1800:40::/42, + 2a13:1800:90::/44, + 2a13:1800:a0::/43, + 2a13:1800:c0::/42, + 2a13:1800:100::/40, + 2a13:1800:200::/40, + 2a13:1800:310::/44, + 2a13:1800:320::/43, + 2a13:1800:340::/42, + 2a13:1800:380::/41, + 2a13:1800:400::/38, + 2a13:1800:800::/37, + 2a13:1800:1000::/36, + 2a13:1800:2000::/35, + 2a13:1800:4000::/34, + 2a13:1800:8000::/33, + 2a13:1801::/40, + 2a13:1801:100::/41, + 2a13:1801:1a0::/43, + 2a13:1801:1c0::/42, + 2a13:1801:210::/44, + 2a13:1801:220::/43, + 2a13:1801:240::/42, + 2a13:1801:280::/41, + 2a13:1801:300::/40, + 2a13:1801:400::/38, + 2a13:1801:800::/37, + 2a13:1801:1000::/36, + 2a13:1801:2000::/35, + 2a13:1801:4000::/34, + 2a13:1801:8000::/33, + 2a13:1802:20::/43, + 2a13:1802:40::/42, + 2a13:1802:80::/41, + 2a13:1802:100::/40, + 2a13:1802:200::/39, + 2a13:1802:400::/38, + 2a13:1802:800::/37, + 2a13:1802:1000::/36, + 2a13:1802:2000::/35, + 2a13:1802:4000::/34, + 2a13:1802:8000::/34, + 2a13:1802:c000::/48, + 2a13:1802:c002::/47, + 2a13:1802:c004::/46, + 2a13:1802:c008::/45, + 2a13:1802:c020::/43, + 2a13:1802:c040::/42, + 2a13:1802:c080::/41, + 2a13:1802:c100::/40, + 2a13:1802:c200::/39, + 2a13:1802:c400::/38, + 2a13:1802:c800::/37, + 2a13:1802:d000::/36, + 2a13:1802:e000::/35, + 2a13:1803::/32, + 2a13:1804::/30, + 2a13:8b40::/29, + 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:9041::/48 + } +} diff --git a/clashoo/files/usr/share/clashoo/nftables/geoip_cn.nft b/clashoo/files/usr/share/clashoo/nftables/geoip_cn.nft new file mode 100644 index 00000000..26ac0cf7 --- /dev/null +++ b/clashoo/files/usr/share/clashoo/nftables/geoip_cn.nft @@ -0,0 +1,6529 @@ +set clashoo_china { + type ipv4_addr; + flags interval; + auto-merge; + elements = { + 1.0.1.0/24, + 1.0.2.0/23, + 1.0.8.0/21, + 1.0.32.0/19, + 1.1.0.0/24, + 1.1.2.0/23, + 1.1.4.0/22, + 1.1.8.0/21, + 1.1.16.0/20, + 1.1.32.0/19, + 1.2.0.0/23, + 1.2.2.0/24, + 1.2.4.0/22, + 1.2.8.0/21, + 1.2.16.0/20, + 1.2.32.0/19, + 1.2.64.0/18, + 1.3.0.0/16, + 1.4.1.0/24, + 1.4.2.0/23, + 1.4.4.0/22, + 1.4.8.0/21, + 1.4.16.0/20, + 1.4.32.0/19, + 1.4.64.0/18, + 1.8.0.0/16, + 1.10.0.0/21, + 1.10.8.0/23, + 1.10.11.0/24, + 1.10.12.0/22, + 1.10.16.0/20, + 1.10.32.0/19, + 1.10.64.0/18, + 1.12.0.0/14, + 1.24.0.0/13, + 1.45.0.0/16, + 1.48.0.0/14, + 1.56.0.0/13, + 1.68.0.0/14, + 1.80.0.0/12, + 1.116.0.0/15, + 1.118.0.0/23, + 1.118.4.0/22, + 1.118.8.0/21, + 1.118.16.0/20, + 1.118.37.0/24, + 1.118.38.0/23, + 1.118.40.0/21, + 1.118.56.0/21, + 1.118.96.0/19, + 1.118.128.0/17, + 1.119.0.0/16, + 1.180.0.0/14, + 1.184.0.0/15, + 1.188.0.0/14, + 1.192.0.0/13, + 1.202.0.0/15, + 1.204.0.0/14, + 5.10.138.0/23, + 5.10.140.0/24, + 5.10.143.0/24, + 5.181.224.0/23, + 8.128.4.0/22, + 8.128.32.0/19, + 8.128.64.0/19, + 8.128.96.0/20, + 8.129.0.0/16, + 8.130.0.0/15, + 8.132.0.0/14, + 8.136.0.0/13, + 8.144.0.0/14, + 8.148.0.0/19, + 8.148.36.0/22, + 8.148.41.0/24, + 8.148.43.0/24, + 8.148.64.0/18, + 8.148.128.0/17, + 8.149.0.0/16, + 8.150.0.0/19, + 8.150.32.0/21, + 8.150.64.0/23, + 8.152.0.0/13, + 8.160.0.0/15, + 8.162.0.0/17, + 8.163.0.0/16, + 8.164.0.0/14, + 8.168.0.0/15, + 8.176.0.0/17, + 8.176.128.0/19, + 8.208.0.0/17, + 8.208.128.0/21, + 8.208.140.0/22, + 8.208.144.0/20, + 8.208.160.0/19, + 8.208.192.0/18, + 8.209.0.0/19, + 8.209.36.0/22, + 8.209.40.0/21, + 8.209.48.0/20, + 8.209.64.0/18, + 8.209.128.0/18, + 8.209.192.0/24, + 8.209.194.0/23, + 8.209.196.0/22, + 8.209.200.0/21, + 8.209.208.0/20, + 8.209.224.0/19, + 8.210.0.0/16, + 8.211.0.0/17, + 8.211.128.0/18, + 8.211.192.0/19, + 8.211.224.0/22, + 8.211.232.0/21, + 8.211.240.0/20, + 8.212.0.0/18, + 8.212.64.0/20, + 8.212.80.0/21, + 8.212.88.0/22, + 8.212.92.0/24, + 8.212.94.0/23, + 8.212.96.0/22, + 8.212.104.0/21, + 8.212.112.0/20, + 8.212.128.0/19, + 8.212.160.0/20, + 8.212.176.0/21, + 8.212.188.0/22, + 8.212.192.0/18, + 8.213.0.0/16, + 8.214.0.0/17, + 8.214.128.0/19, + 8.214.164.0/22, + 8.214.168.0/21, + 8.214.176.0/20, + 8.214.192.0/18, + 8.215.0.0/17, + 8.215.128.0/19, + 8.215.160.0/22, + 8.215.168.0/21, + 8.215.176.0/20, + 8.215.192.0/18, + 8.216.0.0/18, + 8.216.64.0/21, + 8.216.72.0/22, + 8.216.80.0/20, + 8.216.96.0/19, + 8.216.128.0/17, + 8.217.0.0/16, + 8.218.0.0/15, + 8.220.0.0/18, + 8.220.64.0/19, + 8.220.96.0/20, + 8.220.112.0/21, + 8.220.120.0/22, + 8.220.128.0/19, + 8.220.160.0/21, + 8.220.172.0/22, + 8.220.176.0/20, + 8.220.192.0/19, + 8.220.224.0/21, + 8.220.232.0/22, + 8.220.240.0/21, + 8.221.0.0/17, + 8.221.128.0/20, + 8.221.144.0/21, + 8.221.156.0/22, + 8.221.160.0/19, + 8.221.192.0/18, + 8.222.0.0/15, + 14.0.0.0/21, + 14.0.12.0/22, + 14.1.0.0/22, + 14.1.24.0/22, + 14.1.108.0/22, + 14.1.112.0/22, + 14.16.0.0/12, + 14.102.128.0/22, + 14.102.180.0/22, + 14.103.0.0/16, + 14.104.0.0/13, + 14.112.0.0/12, + 14.130.0.0/15, + 14.134.0.0/15, + 14.144.0.0/12, + 14.192.60.0/22, + 14.192.76.0/22, + 14.196.0.0/15, + 14.204.0.0/15, + 14.208.0.0/12, + 16.2.142.0/23, + 27.0.128.0/21, + 27.0.160.0/21, + 27.0.188.0/22, + 27.0.204.0/22, + 27.0.208.0/21, + 27.8.0.0/13, + 27.16.0.0/12, + 27.34.232.0/21, + 27.36.0.0/14, + 27.40.0.0/13, + 27.50.40.0/21, + 27.50.128.0/17, + 27.54.72.0/21, + 27.54.152.0/21, + 27.54.192.0/18, + 27.98.208.0/20, + 27.98.224.0/19, + 27.99.128.0/17, + 27.103.0.0/16, + 27.106.128.0/18, + 27.106.204.0/22, + 27.109.32.0/19, + 27.109.124.0/22, + 27.112.0.0/18, + 27.112.80.0/20, + 27.112.112.0/21, + 27.113.128.0/18, + 27.115.0.0/17, + 27.116.44.0/22, + 27.121.72.0/21, + 27.121.120.0/21, + 27.128.0.0/15, + 27.131.220.0/22, + 27.144.0.0/16, + 27.148.0.0/14, + 27.152.0.0/13, + 27.184.0.0/13, + 27.192.0.0/11, + 27.224.0.0/14, + 36.0.0.0/22, + 36.0.8.0/21, + 36.0.16.0/20, + 36.0.32.0/19, + 36.0.64.0/18, + 36.0.128.0/17, + 36.1.0.0/16, + 36.4.0.0/14, + 36.16.0.0/12, + 36.32.0.0/14, + 36.36.0.0/16, + 36.37.0.0/19, + 36.37.36.0/23, + 36.37.39.0/24, + 36.37.40.0/21, + 36.37.48.0/20, + 36.40.0.0/13, + 36.48.0.0/15, + 36.50.226.0/23, + 36.50.254.0/23, + 36.51.0.0/16, + 36.56.0.0/13, + 36.96.0.0/11, + 36.128.0.0/13, + 36.136.0.0/14, + 36.140.0.0/17, + 36.140.128.0/18, + 36.140.192.0/20, + 36.140.208.0/21, + 36.140.216.0/23, + 36.140.218.0/24, + 36.140.220.0/22, + 36.140.224.0/19, + 36.141.0.0/16, + 36.142.0.0/15, + 36.144.0.0/12, + 36.160.0.0/11, + 36.192.0.0/11, + 36.248.0.0/14, + 36.254.0.0/16, + 36.255.116.0/22, + 36.255.128.0/22, + 36.255.164.0/22, + 36.255.172.0/22, + 36.255.176.0/22, + 39.0.0.0/24, + 39.0.2.0/23, + 39.0.4.0/22, + 39.0.8.0/21, + 39.0.16.0/20, + 39.0.32.0/19, + 39.0.64.0/18, + 39.0.128.0/17, + 39.64.0.0/11, + 39.96.0.0/16, + 39.97.0.0/17, + 39.97.128.0/18, + 39.97.192.0/21, + 39.97.200.0/22, + 39.97.208.0/20, + 39.97.224.0/19, + 39.98.0.0/15, + 39.100.0.0/14, + 39.104.0.0/14, + 39.108.0.0/16, + 39.128.0.0/10, + 40.72.0.0/15, + 40.125.128.0/17, + 40.126.64.0/18, + 40.162.0.0/16, + 42.0.0.0/22, + 42.0.8.0/21, + 42.0.16.0/21, + 42.0.24.0/22, + 42.0.32.0/19, + 42.0.128.0/18, + 42.0.192.0/21, + 42.0.201.0/24, + 42.0.202.0/23, + 42.0.204.0/22, + 42.0.208.0/20, + 42.0.224.0/19, + 42.1.0.0/19, + 42.1.32.0/20, + 42.1.48.0/21, + 42.1.56.0/22, + 42.1.128.0/17, + 42.4.0.0/14, + 42.48.0.0/13, + 42.56.0.0/14, + 42.62.0.0/17, + 42.62.128.0/19, + 42.62.160.0/20, + 42.62.180.0/22, + 42.62.184.0/21, + 42.63.0.0/16, + 42.80.0.0/15, + 42.83.64.0/20, + 42.83.80.0/22, + 42.83.88.0/21, + 42.83.96.0/19, + 42.83.128.0/17, + 42.84.0.0/14, + 42.88.0.0/13, + 42.96.64.0/19, + 42.96.96.0/21, + 42.96.108.0/22, + 42.96.112.0/20, + 42.96.128.0/17, + 42.97.0.0/16, + 42.99.0.0/18, + 42.99.64.0/19, + 42.99.96.0/20, + 42.99.112.0/22, + 42.99.120.0/21, + 42.100.0.0/14, + 42.120.0.0/15, + 42.122.0.0/16, + 42.123.0.0/19, + 42.123.36.0/22, + 42.123.40.0/21, + 42.123.48.0/20, + 42.123.64.0/18, + 42.123.128.0/17, + 42.128.0.0/12, + 42.156.0.0/19, + 42.156.36.0/22, + 42.156.40.0/21, + 42.156.48.0/20, + 42.156.64.0/18, + 42.156.128.0/17, + 42.157.0.0/16, + 42.158.0.0/15, + 42.160.0.0/12, + 42.176.0.0/13, + 42.184.0.0/15, + 42.186.0.0/16, + 42.187.0.0/18, + 42.187.64.0/19, + 42.187.96.0/20, + 42.187.112.0/21, + 42.187.120.0/22, + 42.187.128.0/17, + 42.192.0.0/13, + 42.201.0.0/17, + 42.202.0.0/15, + 42.204.0.0/14, + 42.208.0.0/12, + 42.224.0.0/12, + 42.240.0.0/17, + 42.240.128.0/18, + 42.240.192.0/19, + 42.240.224.0/20, + 42.240.240.0/21, + 42.240.248.0/23, + 42.242.0.0/15, + 42.244.0.0/14, + 42.248.0.0/13, + 43.90.0.0/16, + 43.92.0.0/15, + 43.94.0.0/18, + 43.96.0.0/23, + 43.96.3.0/24, + 43.96.4.0/22, + 43.96.8.0/22, + 43.96.18.0/24, + 43.96.20.0/22, + 43.96.24.0/22, + 43.96.32.0/22, + 43.96.40.0/24, + 43.96.42.0/24, + 43.96.44.0/23, + 43.96.46.0/24, + 43.96.48.0/21, + 43.96.59.0/24, + 43.96.60.0/24, + 43.96.62.0/23, + 43.96.64.0/21, + 43.96.72.0/22, + 43.96.78.0/24, + 43.96.80.0/23, + 43.96.85.0/24, + 43.96.86.0/24, + 43.96.88.0/23, + 43.96.90.0/24, + 43.96.92.0/22, + 43.96.96.0/23, + 43.96.100.0/23, + 43.96.103.0/24, + 43.96.104.0/23, + 43.96.107.0/24, + 43.96.109.0/24, + 43.96.110.0/23, + 43.96.112.0/22, + 43.96.116.0/24, + 43.96.118.0/23, + 43.96.120.0/24, + 43.96.122.0/24, + 43.96.124.0/23, + 43.96.126.0/24, + 43.97.0.0/17, + 43.98.0.0/15, + 43.100.0.0/15, + 43.102.0.0/17, + 43.102.192.0/18, + 43.103.0.0/16, + 43.104.0.0/14, + 43.108.0.0/16, + 43.110.0.0/15, + 43.112.0.0/15, + 43.114.0.0/16, + 43.116.0.0/17, + 43.116.160.0/24, + 43.116.192.0/18, + 43.117.0.0/16, + 43.118.0.0/17, + 43.118.128.0/18, + 43.119.0.0/16, + 43.120.0.0/15, + 43.122.0.0/16, + 43.123.0.0/17, + 43.123.128.0/18, + 43.124.0.0/16, + 43.125.0.0/19, + 43.125.32.0/20, + 43.125.48.0/21, + 43.126.0.0/17, + 43.136.0.0/13, + 43.144.0.0/15, + 43.148.0.0/14, + 43.176.0.0/12, + 43.192.0.0/14, + 43.196.0.0/15, + 43.224.12.0/22, + 43.224.24.0/22, + 43.224.44.0/22, + 43.224.52.0/22, + 43.224.56.0/22, + 43.224.68.0/22, + 43.224.72.0/22, + 43.224.80.0/22, + 43.224.100.0/22, + 43.224.160.0/22, + 43.224.176.0/22, + 43.224.184.0/22, + 43.224.200.0/21, + 43.224.208.0/21, + 43.224.216.0/22, + 43.224.240.0/22, + 43.225.76.0/22, + 43.225.84.0/22, + 43.225.120.0/22, + 43.225.175.0/24, + 43.225.180.0/22, + 43.225.208.0/22, + 43.225.216.0/21, + 43.225.224.0/20, + 43.225.240.0/21, + 43.225.252.0/22, + 43.226.32.0/19, + 43.226.64.0/19, + 43.226.96.0/20, + 43.226.112.0/21, + 43.226.120.0/22, + 43.226.128.0/18, + 43.226.192.0/20, + 43.226.208.0/21, + 43.226.236.0/22, + 43.226.240.0/20, + 43.227.0.0/21, + 43.227.8.0/22, + 43.227.32.0/19, + 43.227.64.0/19, + 43.227.96.0/21, + 43.227.104.0/22, + 43.227.136.0/21, + 43.227.144.0/22, + 43.227.152.0/21, + 43.227.160.0/20, + 43.227.176.0/21, + 43.227.188.0/22, + 43.227.192.0/19, + 43.227.232.0/22, + 43.227.248.0/21, + 43.228.0.0/18, + 43.228.64.0/21, + 43.228.76.0/22, + 43.228.100.0/22, + 43.228.116.0/22, + 43.228.120.0/22, + 43.228.132.0/22, + 43.228.136.0/22, + 43.228.148.0/22, + 43.228.152.0/22, + 43.228.188.0/22, + 43.228.204.0/22, + 43.228.240.0/22, + 43.229.40.0/22, + 43.229.48.0/22, + 43.229.56.0/22, + 43.229.96.0/22, + 43.229.136.0/21, + 43.229.144.0/22, + 43.229.168.0/21, + 43.229.176.0/20, + 43.229.192.0/21, + 43.229.216.0/21, + 43.229.232.0/21, + 43.230.20.0/22, + 43.230.32.0/22, + 43.230.68.0/22, + 43.230.72.0/22, + 43.230.124.0/22, + 43.230.220.0/22, + 43.230.224.0/19, + 43.231.32.0/20, + 43.231.80.0/20, + 43.231.96.0/20, + 43.231.136.0/21, + 43.231.144.0/20, + 43.231.160.0/20, + 43.231.176.0/21, + 43.236.0.0/16, + 43.237.0.0/17, + 43.237.128.0/18, + 43.237.192.0/22, + 43.237.200.0/21, + 43.237.208.0/20, + 43.237.224.0/19, + 43.238.0.0/16, + 43.239.0.0/19, + 43.239.32.0/20, + 43.239.48.0/22, + 43.239.116.0/22, + 43.239.120.0/22, + 43.239.172.0/22, + 43.239.176.0/23, + 43.239.179.0/24, + 43.240.0.0/22, + 43.240.56.0/21, + 43.240.68.0/22, + 43.240.72.0/21, + 43.240.84.0/22, + 43.240.124.0/22, + 43.240.128.0/21, + 43.240.136.0/22, + 43.240.156.0/22, + 43.240.192.0/19, + 43.240.240.0/20, + 43.241.0.0/20, + 43.241.16.0/21, + 43.241.48.0/22, + 43.241.76.0/22, + 43.241.80.0/20, + 43.241.100.0/23, + 43.241.112.0/22, + 43.241.168.0/21, + 43.241.176.0/21, + 43.241.184.0/22, + 43.241.208.0/20, + 43.241.224.0/20, + 43.241.240.0/22, + 43.241.248.0/22, + 43.242.8.0/21, + 43.242.16.0/20, + 43.242.48.0/22, + 43.242.53.0/24, + 43.242.54.0/24, + 43.242.56.0/21, + 43.242.64.0/22, + 43.242.72.0/21, + 43.242.80.0/20, + 43.242.96.0/22, + 43.242.144.0/20, + 43.242.160.0/21, + 43.242.168.0/22, + 43.242.180.0/22, + 43.242.189.0/24, + 43.242.190.0/23, + 43.242.192.0/21, + 43.242.204.0/22, + 43.242.216.0/21, + 43.242.252.0/22, + 43.243.4.0/22, + 43.243.8.0/21, + 43.243.16.0/22, + 43.243.88.0/22, + 43.243.128.0/22, + 43.243.136.0/22, + 43.243.144.0/21, + 43.243.156.0/22, + 43.243.180.0/22, + 43.243.228.0/22, + 43.243.232.0/22, + 43.243.244.0/22, + 43.246.0.0/18, + 43.246.64.0/19, + 43.246.96.0/22, + 43.246.112.0/22, + 43.246.228.0/22, + 43.247.4.0/22, + 43.247.8.0/22, + 43.247.44.0/22, + 43.247.48.0/22, + 43.247.68.0/22, + 43.247.76.0/22, + 43.247.84.0/22, + 43.247.88.0/21, + 43.247.96.0/21, + 43.247.108.0/22, + 43.247.112.0/22, + 43.247.148.0/22, + 43.247.152.0/22, + 43.247.176.0/20, + 43.247.196.0/22, + 43.247.200.0/21, + 43.247.208.0/20, + 43.247.224.0/22, + 43.247.232.0/21, + 43.247.240.0/20, + 43.248.0.0/21, + 43.248.20.0/22, + 43.248.28.0/22, + 43.248.48.0/22, + 43.248.76.0/22, + 43.248.80.0/20, + 43.248.96.0/19, + 43.248.128.0/20, + 43.248.144.0/21, + 43.248.176.0/20, + 43.248.192.0/20, + 43.248.208.0/22, + 43.248.228.0/22, + 43.248.232.0/22, + 43.248.244.0/22, + 43.249.4.0/22, + 43.249.120.0/22, + 43.249.132.0/22, + 43.249.136.0/22, + 43.249.144.0/20, + 43.249.160.0/21, + 43.249.168.0/22, + 43.249.192.0/22, + 43.249.236.0/22, + 43.250.4.0/22, + 43.250.12.0/22, + 43.250.16.0/21, + 43.250.28.0/22, + 43.250.32.0/21, + 43.250.96.0/21, + 43.250.108.0/22, + 43.250.112.0/22, + 43.250.128.0/22, + 43.250.144.0/21, + 43.250.160.0/22, + 43.250.168.0/22, + 43.250.176.0/22, + 43.250.180.0/23, + 43.250.200.0/22, + 43.250.212.0/22, + 43.250.216.0/21, + 43.250.236.0/22, + 43.250.244.0/22, + 43.251.4.0/22, + 43.251.36.0/22, + 43.251.118.0/23, + 43.251.192.0/22, + 43.251.232.0/22, + 43.251.244.0/22, + 43.252.48.0/22, + 43.252.56.0/22, + 43.254.0.0/21, + 43.254.8.0/22, + 43.254.24.0/22, + 43.254.36.0/22, + 43.254.44.0/22, + 43.254.52.0/22, + 43.254.72.0/22, + 43.254.84.0/22, + 43.254.88.0/21, + 43.254.100.0/22, + 43.254.104.0/22, + 43.254.112.0/21, + 43.254.128.0/22, + 43.254.136.0/21, + 43.254.144.0/20, + 43.254.168.0/21, + 43.254.180.0/22, + 43.254.184.0/21, + 43.254.192.0/21, + 43.254.200.0/22, + 43.254.208.0/22, + 43.254.220.0/22, + 43.254.224.0/20, + 43.254.240.0/22, + 43.254.248.0/21, + 43.255.0.0/21, + 43.255.8.0/22, + 43.255.48.0/22, + 43.255.64.0/20, + 43.255.84.0/22, + 43.255.96.0/22, + 43.255.144.0/22, + 43.255.176.0/22, + 43.255.184.0/22, + 43.255.192.0/22, + 43.255.200.0/21, + 43.255.208.0/21, + 43.255.224.0/21, + 43.255.232.0/22, + 43.255.244.0/22, + 44.30.120.0/24, + 45.40.192.0/18, + 45.65.16.0/20, + 45.112.132.0/22, + 45.112.188.0/22, + 45.112.208.0/20, + 45.112.228.0/22, + 45.112.232.0/21, + 45.113.12.0/22, + 45.113.16.0/20, + 45.113.40.0/22, + 45.113.52.0/22, + 45.113.56.0/22, + 45.113.72.0/22, + 45.113.144.0/21, + 45.113.168.0/22, + 45.113.176.0/22, + 45.113.184.0/22, + 45.113.200.0/21, + 45.113.208.0/20, + 45.113.240.0/22, + 45.113.252.0/22, + 45.114.0.0/22, + 45.114.32.0/22, + 45.114.40.0/22, + 45.114.52.0/22, + 45.114.96.0/22, + 45.114.136.0/22, + 45.114.196.0/22, + 45.114.200.0/22, + 45.114.228.0/22, + 45.114.252.0/22, + 45.115.18.0/23, + 45.115.44.0/22, + 45.115.100.0/22, + 45.115.120.0/22, + 45.115.132.0/22, + 45.115.144.0/22, + 45.115.156.0/22, + 45.115.164.0/22, + 45.115.200.0/22, + 45.115.212.0/22, + 45.115.244.0/22, + 45.115.248.0/22, + 45.116.16.0/22, + 45.116.24.0/22, + 45.116.32.0/21, + 45.116.52.0/22, + 45.116.96.0/21, + 45.116.140.0/22, + 45.116.152.0/22, + 45.116.208.0/22, + 45.117.8.0/22, + 45.117.20.0/22, + 45.117.68.0/22, + 45.117.124.0/22, + 45.117.252.0/22, + 45.119.42.0/23, + 45.119.60.0/22, + 45.119.64.0/21, + 45.119.72.0/22, + 45.119.104.0/22, + 45.119.116.0/22, + 45.119.232.0/22, + 45.120.100.0/22, + 45.120.140.0/22, + 45.120.164.0/22, + 45.120.240.0/22, + 45.121.52.0/22, + 45.121.64.0/21, + 45.121.72.0/22, + 45.121.92.0/22, + 45.121.172.0/22, + 45.121.176.0/22, + 45.121.215.0/24, + 45.121.240.0/20, + 45.122.0.0/19, + 45.122.32.0/21, + 45.122.40.0/22, + 45.122.60.0/22, + 45.122.64.0/19, + 45.122.96.0/20, + 45.122.112.0/21, + 45.122.160.0/19, + 45.122.192.0/20, + 45.122.208.0/21, + 45.122.216.0/22, + 45.123.28.0/22, + 45.123.32.0/21, + 45.123.44.0/22, + 45.123.48.0/20, + 45.123.64.0/20, + 45.123.80.0/21, + 45.123.88.0/22, + 45.123.120.0/22, + 45.123.128.0/21, + 45.123.136.0/22, + 45.123.148.0/22, + 45.123.152.0/21, + 45.123.164.0/22, + 45.123.168.0/21, + 45.123.176.0/21, + 45.123.184.0/22, + 45.123.204.0/22, + 45.123.212.0/22, + 45.123.224.0/19, + 45.124.0.0/22, + 45.124.20.0/22, + 45.124.28.0/22, + 45.124.32.0/21, + 45.124.68.0/22, + 45.124.76.0/22, + 45.124.80.0/22, + 45.124.100.0/22, + 45.124.124.0/22, + 45.124.172.0/22, + 45.124.176.0/22, + 45.124.208.0/22, + 45.124.248.0/22, + 45.125.24.0/22, + 45.125.44.0/22, + 45.125.52.0/22, + 45.125.56.0/22, + 45.125.76.0/22, + 45.125.80.0/20, + 45.125.96.0/21, + 45.125.136.0/22, + 45.126.48.0/21, + 45.126.100.0/22, + 45.126.108.0/22, + 45.126.112.0/21, + 45.126.120.0/22, + 45.126.212.0/22, + 45.126.220.0/22, + 45.127.8.0/21, + 45.127.128.0/22, + 45.127.144.0/21, + 45.127.156.0/22, + 45.199.179.0/24, + 45.248.8.0/22, + 45.248.80.0/21, + 45.248.96.0/20, + 45.248.128.0/21, + 45.248.204.0/22, + 45.248.208.0/20, + 45.248.224.0/19, + 45.249.0.0/21, + 45.249.12.0/22, + 45.249.16.0/20, + 45.249.32.0/21, + 45.249.112.0/22, + 45.249.188.0/22, + 45.249.192.0/20, + 45.249.208.0/21, + 45.250.12.0/22, + 45.250.16.0/22, + 45.250.28.0/22, + 45.250.32.0/21, + 45.250.40.0/22, + 45.250.76.0/22, + 45.250.80.0/20, + 45.250.96.0/22, + 45.250.104.0/21, + 45.250.112.0/20, + 45.250.128.0/20, + 45.250.144.0/21, + 45.250.152.0/22, + 45.250.164.0/22, + 45.250.180.0/22, + 45.250.184.0/21, + 45.250.192.0/22, + 45.251.0.0/22, + 45.251.8.0/22, + 45.251.16.0/21, + 45.251.52.0/22, + 45.251.84.0/22, + 45.251.88.0/21, + 45.251.96.0/21, + 45.251.120.0/21, + 45.251.140.0/22, + 45.251.144.0/20, + 45.251.160.0/19, + 45.251.192.0/19, + 45.251.224.0/22, + 45.251.248.0/23, + 45.252.0.0/21, + 45.252.9.0/24, + 45.252.10.0/23, + 45.252.12.0/22, + 45.252.16.0/20, + 45.252.32.0/20, + 45.252.48.0/22, + 45.252.88.0/21, + 45.252.96.0/22, + 45.252.104.0/21, + 45.252.112.0/20, + 45.252.128.0/20, + 45.252.148.0/22, + 45.252.152.0/21, + 45.252.160.0/20, + 45.252.176.0/22, + 45.252.192.0/19, + 45.252.224.0/21, + 45.252.232.0/22, + 45.253.0.0/18, + 45.253.64.0/20, + 45.253.80.0/21, + 45.253.92.0/22, + 45.253.96.0/20, + 45.253.112.0/21, + 45.253.120.0/22, + 45.253.132.0/22, + 45.253.136.0/21, + 45.253.144.0/20, + 45.253.160.0/19, + 45.253.192.0/19, + 45.253.224.0/20, + 45.253.240.0/22, + 45.254.0.0/20, + 45.254.16.0/21, + 45.254.28.0/22, + 45.254.40.0/22, + 45.254.48.0/20, + 45.254.64.0/18, + 45.254.128.0/18, + 45.254.192.0/19, + 45.254.224.0/21, + 45.254.236.0/22, + 45.254.240.0/22, + 45.254.248.0/22, + 45.255.0.0/18, + 45.255.64.0/19, + 45.255.96.0/20, + 45.255.112.0/21, + 45.255.120.0/22, + 45.255.136.0/21, + 45.255.144.0/20, + 45.255.160.0/19, + 45.255.192.0/19, + 45.255.224.0/20, + 45.255.240.0/21, + 45.255.248.0/22, + 47.52.0.0/16, + 47.56.0.0/16, + 47.57.0.0/17, + 47.57.128.0/18, + 47.57.192.0/22, + 47.57.196.0/24, + 47.57.198.0/23, + 47.57.200.0/21, + 47.57.208.0/20, + 47.57.224.0/19, + 47.74.0.0/15, + 47.76.0.0/16, + 47.77.0.0/20, + 47.77.16.0/21, + 47.77.24.0/22, + 47.77.32.0/19, + 47.77.64.0/19, + 47.77.96.0/20, + 47.77.112.0/23, + 47.77.128.0/17, + 47.78.0.0/15, + 47.80.0.0/14, + 47.84.0.0/16, + 47.85.0.0/17, + 47.85.128.0/23, + 47.85.131.0/24, + 47.85.132.0/22, + 47.85.136.0/21, + 47.85.144.0/20, + 47.85.160.0/19, + 47.85.192.0/18, + 47.86.0.0/16, + 47.87.0.0/20, + 47.87.16.0/21, + 47.87.32.0/19, + 47.87.64.0/21, + 47.87.72.0/24, + 47.87.80.0/20, + 47.87.96.0/19, + 47.87.128.0/20, + 47.87.144.0/21, + 47.87.156.0/22, + 47.87.160.0/19, + 47.87.192.0/18, + 47.88.0.0/16, + 47.89.0.0/18, + 47.89.72.0/21, + 47.89.80.0/22, + 47.89.84.0/24, + 47.89.88.0/23, + 47.89.90.0/24, + 47.89.92.0/22, + 47.89.96.0/20, + 47.89.122.0/23, + 47.89.124.0/23, + 47.89.128.0/17, + 47.90.0.0/17, + 47.90.128.0/19, + 47.90.160.0/21, + 47.90.168.0/22, + 47.90.176.0/20, + 47.90.192.0/18, + 47.91.0.0/16, + 47.92.0.0/14, + 47.96.0.0/11, + 47.235.0.0/21, + 47.235.8.0/22, + 47.235.12.0/23, + 47.235.16.0/20, + 47.236.0.0/17, + 47.236.128.0/18, + 47.236.192.0/20, + 47.236.208.0/21, + 47.236.220.0/22, + 47.236.224.0/19, + 47.237.0.0/17, + 47.237.128.0/18, + 47.237.192.0/19, + 47.237.224.0/20, + 47.237.240.0/21, + 47.237.249.0/24, + 47.237.250.0/23, + 47.237.252.0/22, + 47.238.0.0/20, + 47.238.16.0/21, + 47.238.28.0/22, + 47.238.32.0/19, + 47.238.64.0/18, + 47.238.128.0/17, + 47.239.0.0/16, + 47.240.0.0/14, + 47.244.0.0/16, + 47.245.0.0/17, + 47.245.128.0/20, + 47.245.144.0/21, + 47.245.152.0/22, + 47.245.160.0/19, + 47.245.192.0/20, + 47.245.208.0/21, + 47.245.216.0/22, + 47.245.224.0/19, + 47.246.32.0/22, + 47.246.66.0/23, + 47.246.68.0/23, + 47.246.72.0/21, + 47.246.82.0/23, + 47.246.84.0/22, + 47.246.88.0/23, + 47.246.91.0/24, + 47.246.92.0/23, + 47.246.96.0/22, + 47.246.101.0/24, + 47.246.102.0/23, + 47.246.104.0/21, + 47.246.120.0/24, + 47.246.122.0/23, + 47.246.124.0/23, + 47.246.128.0/23, + 47.246.131.0/24, + 47.246.132.0/23, + 47.246.135.0/24, + 47.246.136.0/21, + 47.246.144.0/22, + 47.246.150.0/23, + 47.246.152.0/22, + 47.246.157.0/24, + 47.246.158.0/23, + 47.246.160.0/23, + 47.246.162.0/24, + 47.246.164.0/22, + 47.246.168.0/21, + 47.246.176.0/20, + 47.246.192.0/20, + 47.246.208.0/23, + 47.246.212.0/22, + 47.246.216.0/22, + 47.250.0.0/18, + 47.250.68.0/22, + 47.250.72.0/21, + 47.250.80.0/20, + 47.250.96.0/21, + 47.250.108.0/22, + 47.250.112.0/20, + 47.250.128.0/17, + 47.251.0.0/17, + 47.251.132.0/22, + 47.251.136.0/21, + 47.251.144.0/20, + 47.251.160.0/19, + 47.251.196.0/22, + 47.251.200.0/21, + 47.251.208.0/20, + 47.251.224.0/19, + 47.252.0.0/16, + 47.253.0.0/18, + 47.253.64.0/19, + 47.253.96.0/20, + 47.253.112.0/21, + 47.253.120.0/22, + 47.253.128.0/17, + 47.254.0.0/16, + 49.4.0.0/17, + 49.4.128.0/22, + 49.4.160.0/20, + 49.4.178.0/23, + 49.4.180.0/22, + 49.4.184.0/21, + 49.4.192.0/18, + 49.5.0.0/16, + 49.6.0.0/15, + 49.51.0.0/19, + 49.51.54.0/23, + 49.51.56.0/21, + 49.51.88.0/21, + 49.51.96.0/19, + 49.51.226.0/23, + 49.52.0.0/14, + 49.64.0.0/11, + 49.112.0.0/13, + 49.120.0.0/14, + 49.128.0.0/24, + 49.128.2.0/23, + 49.128.4.0/22, + 49.140.0.0/15, + 49.152.0.0/14, + 49.208.0.0/14, + 49.213.60.0/23, + 49.220.0.0/14, + 49.232.0.0/14, + 49.239.0.0/18, + 49.239.192.0/18, + 49.246.224.0/23, + 49.246.228.0/22, + 49.246.232.0/23, + 49.246.236.0/22, + 49.246.240.0/20, + 52.80.0.0/14, + 52.130.0.0/15, + 54.222.0.0/15, + 57.176.0.0/15, + 58.14.0.0/21, + 58.14.16.0/20, + 58.14.32.0/19, + 58.14.64.0/18, + 58.14.128.0/17, + 58.15.0.0/16, + 58.16.0.0/13, + 58.24.0.0/15, + 58.30.0.0/15, + 58.32.0.0/11, + 58.65.232.0/21, + 58.67.128.0/17, + 58.68.224.0/19, + 58.82.0.0/17, + 58.83.0.0/16, + 58.87.64.0/18, + 58.99.128.0/17, + 58.100.0.0/15, + 58.116.0.0/14, + 58.128.0.0/13, + 58.144.0.0/16, + 58.154.0.0/15, + 58.192.0.0/11, + 58.240.0.0/12, + 59.32.0.0/11, + 59.64.0.0/12, + 59.80.0.0/14, + 59.107.0.0/17, + 59.107.226.0/24, + 59.107.255.0/24, + 59.108.0.0/14, + 59.151.0.0/17, + 59.152.16.0/20, + 59.152.32.0/21, + 59.152.64.0/20, + 59.152.112.0/21, + 59.153.4.0/22, + 59.153.32.0/22, + 59.153.60.0/22, + 59.153.64.0/21, + 59.153.72.0/22, + 59.153.92.0/22, + 59.153.136.0/22, + 59.153.152.0/22, + 59.153.164.0/22, + 59.153.168.0/21, + 59.153.176.0/20, + 59.153.192.0/22, + 59.155.0.0/16, + 59.172.0.0/14, + 59.191.0.0/17, + 59.192.0.0/10, + 60.0.0.0/11, + 60.55.0.0/16, + 60.63.0.0/16, + 60.160.0.0/11, + 60.194.0.0/15, + 60.200.0.0/13, + 60.208.0.0/12, + 60.232.0.0/15, + 60.235.0.0/16, + 60.245.128.0/17, + 60.247.0.0/16, + 60.252.0.0/16, + 60.253.128.0/17, + 60.255.0.0/16, + 61.4.80.0/20, + 61.4.176.0/20, + 61.8.160.0/20, + 61.14.212.0/22, + 61.14.216.0/21, + 61.14.240.0/21, + 61.28.0.0/17, + 61.29.128.0/18, + 61.29.192.0/19, + 61.29.224.0/20, + 61.45.128.0/18, + 61.45.224.0/20, + 61.47.128.0/18, + 61.48.0.0/13, + 61.87.192.0/18, + 61.128.0.0/11, + 61.160.0.0/12, + 61.176.0.0/13, + 61.184.0.0/15, + 61.186.0.0/23, + 61.186.3.0/24, + 61.186.4.0/22, + 61.186.8.0/21, + 61.186.16.0/20, + 61.186.32.0/19, + 61.186.64.0/18, + 61.186.128.0/17, + 61.187.0.0/16, + 61.188.0.0/14, + 61.232.0.0/14, + 61.236.0.0/15, + 61.240.0.0/14, + 62.182.190.0/23, + 62.234.0.0/16, + 63.140.0.0/24, + 63.140.3.0/24, + 66.102.240.0/21, + 66.102.248.0/23, + 66.102.251.0/24, + 66.102.252.0/24, + 66.102.254.0/23, + 68.79.0.0/18, + 69.163.104.0/24, + 69.163.106.0/24, + 69.163.123.0/24, + 69.230.192.0/18, + 69.231.128.0/18, + 69.234.192.0/18, + 69.235.128.0/18, + 71.131.192.0/18, + 71.132.0.0/18, + 71.136.64.0/18, + 71.137.0.0/18, + 81.68.0.0/14, + 81.173.18.0/23, + 81.173.20.0/22, + 81.173.28.0/24, + 82.156.0.0/15, + 94.191.0.0/17, + 101.0.0.0/22, + 101.1.0.0/22, + 101.2.172.0/22, + 101.4.0.0/14, + 101.16.0.0/12, + 101.33.128.0/17, + 101.34.0.0/15, + 101.36.0.0/18, + 101.36.64.0/20, + 101.36.82.0/23, + 101.36.84.0/22, + 101.36.88.0/21, + 101.36.128.0/17, + 101.37.0.0/16, + 101.38.0.0/15, + 101.40.0.0/14, + 101.48.0.0/15, + 101.50.8.0/21, + 101.50.56.0/22, + 101.52.0.0/16, + 101.53.100.0/22, + 101.54.0.0/16, + 101.55.224.0/21, + 101.64.0.0/13, + 101.72.0.0/14, + 101.76.0.0/15, + 101.78.0.0/22, + 101.78.32.0/19, + 101.80.0.0/12, + 101.96.0.0/21, + 101.96.8.0/22, + 101.96.16.0/20, + 101.96.128.0/17, + 101.99.96.0/19, + 101.101.64.0/19, + 101.101.100.0/24, + 101.101.102.0/23, + 101.101.104.0/21, + 101.101.112.0/20, + 101.102.64.0/19, + 101.102.100.0/23, + 101.102.102.0/24, + 101.102.104.0/21, + 101.102.112.0/20, + 101.104.0.0/14, + 101.110.64.0/19, + 101.110.96.0/20, + 101.110.116.0/22, + 101.110.120.0/21, + 101.120.0.0/14, + 101.124.0.0/15, + 101.126.0.0/16, + 101.128.0.0/22, + 101.128.8.0/21, + 101.128.16.0/20, + 101.128.32.0/19, + 101.129.0.0/16, + 101.130.0.0/15, + 101.132.0.0/14, + 101.144.0.0/12, + 101.192.0.0/18, + 101.192.68.0/22, + 101.192.84.0/22, + 101.192.88.0/21, + 101.192.132.0/22, + 101.192.164.0/22, + 101.192.168.0/21, + 101.192.176.0/20, + 101.192.192.0/19, + 101.193.0.0/18, + 101.193.100.0/22, + 101.193.108.0/22, + 101.193.112.0/22, + 101.193.120.0/21, + 101.193.132.0/22, + 101.193.164.0/22, + 101.193.168.0/21, + 101.193.176.0/20, + 101.193.192.0/19, + 101.194.0.0/15, + 101.196.0.0/14, + 101.200.0.0/15, + 101.203.128.0/19, + 101.203.160.0/21, + 101.203.172.0/22, + 101.203.176.0/20, + 101.204.0.0/14, + 101.224.0.0/13, + 101.232.0.0/17, + 101.232.128.0/18, + 101.232.200.0/21, + 101.232.212.0/22, + 101.232.220.0/22, + 101.232.224.0/22, + 101.232.232.0/21, + 101.232.240.0/22, + 101.233.0.0/16, + 101.234.64.0/21, + 101.234.76.0/22, + 101.234.80.0/20, + 101.234.96.0/19, + 101.236.0.0/16, + 101.237.0.0/17, + 101.237.128.0/18, + 101.237.192.0/19, + 101.237.224.0/22, + 101.237.230.0/23, + 101.237.243.0/24, + 101.237.244.0/23, + 101.237.246.0/24, + 101.237.250.0/24, + 101.237.253.0/24, + 101.238.0.0/15, + 101.240.0.0/13, + 101.248.0.0/15, + 101.251.0.0/22, + 101.251.8.0/21, + 101.251.16.0/20, + 101.251.32.0/19, + 101.251.64.0/18, + 101.251.128.0/17, + 101.252.0.0/15, + 101.254.0.0/16, + 103.1.8.0/22, + 103.1.20.0/22, + 103.1.24.0/22, + 103.1.72.0/22, + 103.1.88.0/22, + 103.1.168.0/22, + 103.2.108.0/22, + 103.2.156.0/22, + 103.2.164.0/22, + 103.2.188.0/23, + 103.2.200.0/21, + 103.2.208.0/21, + 103.3.84.0/22, + 103.3.88.0/21, + 103.3.96.0/19, + 103.3.128.0/20, + 103.3.148.0/22, + 103.3.152.0/21, + 103.4.56.0/22, + 103.4.168.0/22, + 103.4.184.0/22, + 103.4.224.0/22, + 103.5.36.0/22, + 103.5.52.0/22, + 103.5.56.0/22, + 103.5.152.0/22, + 103.5.168.0/22, + 103.5.192.0/22, + 103.5.252.0/22, + 103.6.76.0/22, + 103.6.220.0/22, + 103.7.140.0/22, + 103.7.212.0/22, + 103.7.216.0/21, + 103.8.0.0/21, + 103.8.8.0/22, + 103.8.32.0/22, + 103.8.52.0/22, + 103.8.68.0/22, + 103.8.108.0/22, + 103.8.156.0/22, + 103.8.200.0/21, + 103.8.220.0/22, + 103.9.8.0/22, + 103.9.24.0/22, + 103.9.108.0/22, + 103.9.152.0/22, + 103.9.248.0/21, + 103.10.0.0/22, + 103.10.16.0/22, + 103.10.84.0/22, + 103.10.140.0/22, + 103.11.16.0/22, + 103.11.168.0/22, + 103.11.180.0/22, + 103.12.32.0/22, + 103.12.92.0/22, + 103.12.98.0/23, + 103.12.136.0/22, + 103.12.184.0/22, + 103.12.232.0/22, + 103.13.12.0/22, + 103.13.124.0/22, + 103.13.196.0/22, + 103.13.244.0/22, + 103.14.84.0/22, + 103.14.132.0/22, + 103.14.136.0/22, + 103.14.156.0/22, + 103.14.240.0/22, + 103.15.4.0/22, + 103.15.8.0/22, + 103.15.16.0/22, + 103.15.96.0/22, + 103.15.200.0/22, + 103.16.52.0/22, + 103.16.80.0/21, + 103.16.88.0/22, + 103.16.108.0/22, + 103.16.124.0/22, + 103.17.40.0/22, + 103.17.64.0/22, + 103.17.120.0/22, + 103.17.136.0/22, + 103.17.160.0/22, + 103.17.204.0/22, + 103.17.228.0/22, + 103.18.186.0/23, + 103.18.192.0/22, + 103.18.208.0/21, + 103.18.224.0/22, + 103.19.12.0/22, + 103.19.40.0/21, + 103.19.64.0/21, + 103.19.72.0/22, + 103.19.232.0/22, + 103.20.12.0/22, + 103.20.32.0/24, + 103.20.34.0/23, + 103.20.46.0/24, + 103.20.68.0/22, + 103.20.112.0/22, + 103.20.128.0/22, + 103.20.160.0/22, + 103.20.248.0/22, + 103.21.98.0/23, + 103.21.102.0/23, + 103.21.112.0/21, + 103.21.140.0/22, + 103.21.176.0/22, + 103.22.0.0/18, + 103.22.64.0/19, + 103.22.100.0/22, + 103.22.104.0/21, + 103.22.112.0/20, + 103.22.188.0/22, + 103.22.228.0/22, + 103.22.252.0/22, + 103.23.8.0/22, + 103.23.56.0/22, + 103.23.160.0/21, + 103.23.176.0/22, + 103.23.228.0/22, + 103.24.24.0/22, + 103.24.116.0/22, + 103.24.128.0/22, + 103.24.144.0/22, + 103.24.176.0/22, + 103.24.184.0/22, + 103.24.220.0/22, + 103.24.228.0/22, + 103.24.252.0/22, + 103.25.9.0/24, + 103.25.20.0/22, + 103.25.24.0/21, + 103.25.32.0/21, + 103.25.40.0/22, + 103.25.48.0/22, + 103.25.64.0/21, + 103.25.148.0/22, + 103.25.156.0/22, + 103.25.216.0/22, + 103.26.0.0/22, + 103.26.64.0/22, + 103.26.76.0/22, + 103.26.132.0/22, + 103.26.156.0/22, + 103.26.160.0/22, + 103.26.228.0/22, + 103.26.240.0/22, + 103.27.4.0/22, + 103.27.12.0/22, + 103.27.24.0/22, + 103.27.56.0/22, + 103.27.96.0/22, + 103.27.212.0/22, + 103.27.240.0/22, + 103.28.4.0/22, + 103.28.8.0/22, + 103.28.184.0/22, + 103.28.204.0/22, + 103.28.212.0/22, + 103.29.16.0/22, + 103.29.24.0/23, + 103.29.128.0/21, + 103.29.136.0/22, + 103.29.236.0/23, + 103.30.20.0/22, + 103.30.96.0/22, + 103.30.104.0/22, + 103.30.148.0/22, + 103.30.228.0/22, + 103.30.236.0/22, + 103.31.0.0/22, + 103.31.48.0/20, + 103.31.64.0/21, + 103.31.148.0/22, + 103.31.160.0/22, + 103.31.168.0/22, + 103.31.200.0/22, + 103.31.242.0/23, + 103.32.0.0/15, + 103.34.0.0/16, + 103.35.0.0/19, + 103.35.32.0/20, + 103.35.48.0/22, + 103.35.104.0/22, + 103.35.117.0/24, + 103.35.119.0/24, + 103.35.180.0/22, + 103.35.220.0/22, + 103.36.28.0/22, + 103.36.36.0/22, + 103.36.56.0/21, + 103.36.64.0/22, + 103.36.72.0/22, + 103.36.96.0/22, + 103.36.132.0/22, + 103.36.136.0/22, + 103.36.160.0/20, + 103.36.192.0/19, + 103.36.224.0/20, + 103.36.240.0/21, + 103.37.12.0/22, + 103.37.16.0/22, + 103.37.24.0/22, + 103.37.44.0/22, + 103.37.52.0/22, + 103.37.56.0/22, + 103.37.72.0/22, + 103.37.100.0/22, + 103.37.104.0/22, + 103.37.136.0/21, + 103.37.144.0/20, + 103.37.160.0/21, + 103.37.172.0/22, + 103.37.176.0/22, + 103.37.188.0/22, + 103.37.208.0/20, + 103.37.252.0/22, + 103.38.0.0/22, + 103.38.32.0/22, + 103.38.40.0/21, + 103.38.56.0/22, + 103.38.76.0/22, + 103.38.84.0/22, + 103.38.92.0/22, + 103.38.96.0/22, + 103.38.116.0/22, + 103.38.132.0/22, + 103.38.140.0/22, + 103.38.224.0/21, + 103.38.232.0/22, + 103.38.252.0/23, + 103.39.64.0/22, + 103.39.88.0/22, + 103.39.100.0/22, + 103.39.104.0/22, + 103.39.160.0/19, + 103.39.200.0/21, + 103.39.208.0/20, + 103.39.224.0/21, + 103.39.232.0/22, + 103.40.12.0/22, + 103.40.16.0/20, + 103.40.32.0/20, + 103.40.88.0/22, + 103.40.158.0/23, + 103.40.173.0/24, + 103.40.174.0/23, + 103.40.192.0/22, + 103.40.212.0/22, + 103.40.220.0/22, + 103.40.228.0/22, + 103.40.232.0/21, + 103.40.240.0/20, + 103.41.0.0/22, + 103.41.52.0/22, + 103.41.116.0/22, + 103.41.140.0/22, + 103.41.148.0/22, + 103.41.152.0/22, + 103.41.160.0/21, + 103.41.220.0/22, + 103.41.224.0/21, + 103.41.232.0/22, + 103.42.8.0/22, + 103.42.24.0/22, + 103.42.32.0/22, + 103.42.64.0/21, + 103.42.76.0/22, + 103.42.232.0/22, + 103.43.96.0/21, + 103.43.104.0/22, + 103.43.124.0/22, + 103.43.132.0/22, + 103.43.184.0/22, + 103.43.192.0/21, + 103.43.208.0/22, + 103.43.220.0/22, + 103.43.224.0/22, + 103.43.240.0/22, + 103.44.56.0/22, + 103.44.80.0/22, + 103.44.120.0/21, + 103.44.144.0/22, + 103.44.168.0/22, + 103.44.176.0/20, + 103.44.192.0/22, + 103.44.196.0/23, + 103.44.199.0/24, + 103.44.200.0/21, + 103.44.224.0/22, + 103.44.236.0/22, + 103.44.240.0/20, + 103.45.0.0/18, + 103.45.72.0/21, + 103.45.80.0/20, + 103.45.96.0/19, + 103.45.128.0/18, + 103.45.192.0/19, + 103.45.224.0/22, + 103.45.248.0/22, + 103.46.0.0/22, + 103.46.12.0/22, + 103.46.16.0/20, + 103.46.32.0/19, + 103.46.64.0/18, + 103.46.128.0/21, + 103.46.136.0/22, + 103.46.152.0/21, + 103.46.160.0/20, + 103.46.176.0/21, + 103.46.244.0/22, + 103.46.248.0/22, + 103.47.4.0/22, + 103.47.20.0/22, + 103.47.36.0/22, + 103.47.40.0/22, + 103.47.48.0/22, + 103.47.80.0/22, + 103.47.96.0/22, + 103.47.108.0/22, + 103.47.116.0/22, + 103.47.120.0/22, + 103.47.136.0/21, + 103.47.212.0/22, + 103.48.52.0/22, + 103.48.92.0/22, + 103.48.148.0/22, + 103.48.152.0/21, + 103.48.202.0/23, + 103.48.216.0/21, + 103.48.224.0/20, + 103.48.240.0/21, + 103.49.12.0/22, + 103.49.20.0/22, + 103.49.72.0/21, + 103.49.96.0/22, + 103.49.108.0/22, + 103.49.176.0/21, + 103.49.197.0/24, + 103.50.36.0/22, + 103.50.44.0/22, + 103.50.48.0/20, + 103.50.64.0/21, + 103.50.72.0/22, + 103.50.108.0/22, + 103.50.112.0/20, + 103.50.132.0/22, + 103.50.136.0/21, + 103.50.172.0/22, + 103.50.176.0/20, + 103.50.192.0/21, + 103.50.200.0/22, + 103.50.220.0/22, + 103.50.224.0/20, + 103.50.240.0/21, + 103.50.248.0/22, + 103.52.40.0/22, + 103.52.72.0/21, + 103.52.80.0/21, + 103.52.96.0/21, + 103.52.104.0/22, + 103.52.160.0/21, + 103.52.172.0/22, + 103.52.176.0/22, + 103.52.184.0/22, + 103.52.196.0/22, + 103.53.64.0/21, + 103.53.92.0/22, + 103.53.100.0/22, + 103.53.124.0/22, + 103.53.128.0/20, + 103.53.144.0/22, + 103.53.180.0/22, + 103.53.204.0/22, + 103.53.208.0/21, + 103.53.236.0/22, + 103.53.248.0/22, + 103.54.8.0/22, + 103.54.48.0/22, + 103.54.160.0/21, + 103.54.212.0/22, + 103.54.240.0/22, + 103.55.80.0/22, + 103.55.120.0/22, + 103.55.152.0/22, + 103.55.172.0/22, + 103.55.204.0/22, + 103.55.208.0/22, + 103.55.228.0/22, + 103.55.236.0/22, + 103.56.20.0/22, + 103.56.32.0/22, + 103.56.56.0/21, + 103.56.72.0/21, + 103.56.94.0/23, + 103.56.100.0/22, + 103.56.104.0/22, + 103.56.140.0/22, + 103.56.152.0/22, + 103.56.184.0/22, + 103.56.200.0/22, + 103.57.12.0/22, + 103.57.52.0/22, + 103.57.56.0/22, + 103.57.76.0/22, + 103.57.136.0/22, + 103.57.196.0/22, + 103.58.24.0/22, + 103.59.76.0/22, + 103.59.112.0/21, + 103.59.120.0/23, + 103.59.124.0/22, + 103.59.128.0/22, + 103.59.148.0/22, + 103.59.164.0/22, + 103.59.168.0/23, + 103.60.44.0/22, + 103.60.164.0/22, + 103.60.228.0/22, + 103.60.236.0/22, + 103.61.60.0/22, + 103.61.104.0/22, + 103.61.140.0/22, + 103.61.152.0/21, + 103.61.160.0/22, + 103.61.172.0/22, + 103.61.176.0/22, + 103.61.188.0/22, + 103.62.24.0/22, + 103.62.53.0/24, + 103.62.55.0/24, + 103.62.72.0/21, + 103.62.80.0/21, + 103.62.88.0/22, + 103.62.96.0/19, + 103.62.128.0/21, + 103.62.156.0/22, + 103.62.160.0/19, + 103.62.192.0/22, + 103.62.204.0/22, + 103.62.208.0/20, + 103.62.224.0/22, + 103.63.32.0/19, + 103.63.64.0/20, + 103.63.80.0/21, + 103.63.88.0/22, + 103.63.140.0/22, + 103.63.144.0/22, + 103.63.152.0/22, + 103.63.160.0/20, + 103.63.176.0/21, + 103.63.184.0/22, + 103.63.192.0/20, + 103.63.208.0/22, + 103.63.240.0/20, + 103.64.0.0/21, + 103.64.24.0/21, + 103.64.32.0/19, + 103.64.64.0/18, + 103.64.140.0/22, + 103.64.144.0/22, + 103.64.152.0/21, + 103.64.160.0/19, + 103.64.192.0/18, + 103.65.0.0/20, + 103.65.16.0/22, + 103.65.48.0/20, + 103.65.64.0/19, + 103.65.100.0/22, + 103.65.104.0/21, + 103.65.112.0/22, + 103.65.144.0/20, + 103.65.160.0/20, + 103.65.204.0/22, + 103.65.224.0/23, + 103.66.32.0/22, + 103.66.40.0/22, + 103.66.108.0/22, + 103.66.200.0/22, + 103.66.240.0/20, + 103.67.0.0/21, + 103.67.8.0/22, + 103.67.100.0/22, + 103.67.104.0/21, + 103.67.112.0/20, + 103.67.128.0/20, + 103.67.144.0/21, + 103.67.172.0/22, + 103.67.192.0/22, + 103.67.212.0/22, + 103.67.252.0/22, + 103.68.64.0/22, + 103.68.88.0/22, + 103.68.100.0/22, + 103.68.128.0/22, + 103.69.16.0/22, + 103.69.62.0/23, + 103.70.8.0/22, + 103.70.14.0/23, + 103.70.148.0/22, + 103.70.224.0/23, + 103.70.227.0/24, + 103.70.236.0/22, + 103.70.252.0/22, + 103.71.0.0/22, + 103.71.49.0/24, + 103.71.68.0/22, + 103.71.72.0/22, + 103.71.80.0/21, + 103.71.88.0/22, + 103.71.120.0/21, + 103.71.128.0/22, + 103.71.144.0/22, + 103.71.196.0/22, + 103.71.200.0/22, + 103.71.232.0/22, + 103.72.12.0/22, + 103.72.16.0/20, + 103.72.32.0/20, + 103.72.48.0/21, + 103.72.112.0/21, + 103.72.124.0/22, + 103.72.128.0/21, + 103.72.172.0/22, + 103.72.180.0/22, + 103.72.224.0/21, + 103.72.233.0/24, + 103.72.234.0/23, + 103.72.236.0/22, + 103.72.240.0/20, + 103.73.0.0/21, + 103.73.10.0/23, + 103.73.12.0/22, + 103.73.16.0/20, + 103.73.48.0/22, + 103.73.116.0/22, + 103.73.120.0/22, + 103.73.128.0/20, + 103.73.144.0/22, + 103.73.168.0/22, + 103.73.176.0/22, + 103.73.204.0/22, + 103.73.208.0/22, + 103.73.244.0/22, + 103.73.248.0/22, + 103.74.24.0/21, + 103.74.32.0/20, + 103.74.48.0/22, + 103.74.56.0/21, + 103.74.80.0/22, + 103.74.126.0/23, + 103.74.148.0/22, + 103.74.152.0/21, + 103.74.204.0/22, + 103.74.232.0/22, + 103.75.82.0/23, + 103.75.88.0/21, + 103.75.104.0/21, + 103.75.112.0/22, + 103.75.120.0/22, + 103.75.128.0/22, + 103.75.144.0/22, + 103.75.152.0/22, + 103.76.60.0/22, + 103.76.64.0/21, + 103.76.72.0/22, + 103.76.92.0/22, + 103.76.216.0/21, + 103.76.224.0/22, + 103.77.28.0/22, + 103.77.52.0/22, + 103.77.56.0/22, + 103.77.88.0/22, + 103.77.132.0/22, + 103.77.148.0/22, + 103.77.220.0/22, + 103.78.56.0/21, + 103.78.64.0/21, + 103.78.124.0/22, + 103.78.172.0/22, + 103.78.176.0/22, + 103.78.196.0/22, + 103.78.228.0/22, + 103.79.24.0/21, + 103.79.36.0/22, + 103.79.40.0/21, + 103.79.56.0/21, + 103.79.64.0/21, + 103.79.80.0/21, + 103.79.136.0/22, + 103.79.188.0/22, + 103.79.192.0/20, + 103.79.208.0/21, + 103.79.228.0/23, + 103.80.44.0/22, + 103.80.72.0/22, + 103.80.176.0/21, + 103.80.184.0/22, + 103.80.192.0/22, + 103.80.200.0/22, + 103.80.232.0/22, + 103.81.4.0/22, + 103.81.8.0/22, + 103.81.16.0/21, + 103.81.44.0/22, + 103.81.48.0/22, + 103.81.96.0/22, + 103.81.120.0/22, + 103.81.148.0/22, + 103.81.164.0/22, + 103.81.184.0/22, + 103.81.200.0/22, + 103.81.232.0/22, + 103.82.60.0/22, + 103.82.68.0/22, + 103.82.84.0/22, + 103.82.104.0/22, + 103.82.224.0/22, + 103.82.236.0/22, + 103.83.44.0/22, + 103.83.52.0/22, + 103.83.60.0/22, + 103.83.64.0/22, + 103.83.72.0/22, + 103.83.112.0/22, + 103.83.180.0/22, + 103.84.0.0/22, + 103.84.12.0/22, + 103.84.16.0/20, + 103.84.48.0/22, + 103.84.64.0/22, + 103.84.72.0/22, + 103.84.170.0/23, + 103.85.44.0/22, + 103.85.48.0/22, + 103.85.84.0/22, + 103.85.136.0/22, + 103.85.144.0/22, + 103.85.164.0/22, + 103.85.168.0/21, + 103.85.176.0/22, + 103.85.186.0/23, + 103.86.28.0/22, + 103.86.32.0/22, + 103.86.60.0/22, + 103.86.204.0/22, + 103.86.208.0/20, + 103.86.224.0/19, + 103.87.0.0/21, + 103.87.20.0/22, + 103.87.32.0/22, + 103.87.96.0/22, + 103.87.132.0/22, + 103.87.180.0/22, + 103.87.224.0/22, + 103.88.4.0/22, + 103.88.8.0/21, + 103.88.16.0/21, + 103.88.32.0/21, + 103.88.60.0/22, + 103.88.64.0/22, + 103.88.72.0/22, + 103.88.96.0/22, + 103.88.164.0/22, + 103.88.212.0/22, + 103.89.28.0/22, + 103.89.96.0/20, + 103.89.112.0/21, + 103.89.148.0/22, + 103.89.172.0/22, + 103.89.184.0/21, + 103.89.192.0/19, + 103.89.224.0/21, + 103.90.52.0/22, + 103.90.56.0/23, + 103.90.92.0/22, + 103.90.100.0/22, + 103.90.104.0/21, + 103.90.112.0/20, + 103.90.128.0/21, + 103.90.152.0/22, + 103.90.168.0/22, + 103.90.176.0/22, + 103.90.188.0/22, + 103.90.192.0/22, + 103.91.36.0/22, + 103.91.40.0/22, + 103.91.108.0/22, + 103.91.112.0/23, + 103.91.138.0/23, + 103.91.152.0/22, + 103.91.176.0/22, + 103.91.200.0/22, + 103.91.208.0/21, + 103.91.236.0/22, + 103.91.252.0/22, + 103.92.0.0/20, + 103.92.48.0/20, + 103.92.64.0/20, + 103.92.80.0/22, + 103.92.86.0/24, + 103.92.88.0/22, + 103.92.108.0/22, + 103.92.124.0/22, + 103.92.132.0/22, + 103.92.156.0/22, + 103.92.164.0/22, + 103.92.168.0/21, + 103.92.176.0/20, + 103.92.192.0/22, + 103.92.236.0/22, + 103.92.240.0/20, + 103.93.0.0/21, + 103.93.28.0/22, + 103.93.84.0/22, + 103.93.142.0/23, + 103.93.152.0/22, + 103.93.180.0/22, + 103.93.204.0/22, + 103.94.12.0/22, + 103.94.20.0/22, + 103.94.32.0/20, + 103.94.72.0/22, + 103.94.88.0/22, + 103.94.116.0/22, + 103.94.160.0/22, + 103.94.200.0/22, + 103.95.52.0/22, + 103.95.68.0/22, + 103.95.88.0/21, + 103.95.128.0/22, + 103.95.136.0/21, + 103.95.144.0/22, + 103.95.152.0/22, + 103.95.216.0/21, + 103.95.236.0/22, + 103.95.240.0/20, + 103.96.8.0/22, + 103.96.124.0/22, + 103.96.136.0/22, + 103.96.152.0/21, + 103.96.160.0/19, + 103.96.192.0/20, + 103.96.208.0/21, + 103.96.216.0/22, + 103.96.224.0/23, + 103.97.8.0/21, + 103.97.16.0/20, + 103.97.36.0/22, + 103.97.40.0/22, + 103.97.56.0/22, + 103.97.60.0/23, + 103.97.64.0/21, + 103.97.72.0/22, + 103.97.112.0/21, + 103.97.144.0/21, + 103.97.188.0/22, + 103.97.192.0/22, + 103.97.229.0/24, + 103.98.0.0/23, + 103.98.40.0/21, + 103.98.48.0/22, + 103.98.56.0/22, + 103.98.80.0/22, + 103.98.88.0/21, + 103.98.96.0/21, + 103.98.124.0/22, + 103.98.136.0/21, + 103.98.144.0/22, + 103.98.164.0/22, + 103.98.168.0/22, + 103.98.180.0/22, + 103.98.196.0/22, + 103.98.216.0/21, + 103.98.224.0/21, + 103.98.232.0/22, + 103.98.240.0/20, + 103.99.53.0/24, + 103.99.54.0/23, + 103.99.56.0/22, + 103.99.62.0/24, + 103.99.104.0/22, + 103.99.116.0/22, + 103.99.120.0/22, + 103.99.152.0/22, + 103.99.220.0/22, + 103.99.232.0/21, + 103.100.0.0/22, + 103.100.32.0/22, + 103.100.40.0/22, + 103.100.48.0/21, + 103.100.56.0/22, + 103.100.62.0/24, + 103.100.64.0/22, + 103.100.88.0/22, + 103.100.116.0/22, + 103.100.144.0/22, + 103.100.236.0/22, + 103.100.240.0/22, + 103.100.248.0/21, + 103.101.8.0/21, + 103.101.60.0/22, + 103.101.120.0/21, + 103.101.144.0/21, + 103.101.180.0/22, + 103.101.184.0/22, + 103.102.76.0/22, + 103.102.80.0/22, + 103.102.168.0/21, + 103.102.180.0/22, + 103.102.184.0/21, + 103.102.192.0/21, + 103.102.200.0/22, + 103.102.208.0/21, + 103.103.12.0/22, + 103.103.16.0/22, + 103.103.36.0/22, + 103.103.72.0/22, + 103.103.188.0/22, + 103.103.200.0/21, + 103.103.220.0/22, + 103.103.224.0/21, + 103.103.232.0/22, + 103.103.248.0/21, + 103.104.0.0/21, + 103.104.36.0/22, + 103.104.40.0/22, + 103.104.64.0/22, + 103.104.104.0/22, + 103.104.152.0/22, + 103.104.188.0/22, + 103.104.198.0/23, + 103.104.252.0/22, + 103.105.0.0/21, + 103.105.12.0/22, + 103.105.16.0/22, + 103.105.60.0/22, + 103.105.116.0/22, + 103.105.133.0/24, + 103.105.134.0/23, + 103.105.180.0/22, + 103.105.184.0/22, + 103.105.200.0/21, + 103.105.220.0/22, + 103.106.36.0/22, + 103.106.40.0/21, + 103.106.60.0/22, + 103.106.68.0/22, + 103.106.96.0/22, + 103.106.120.0/22, + 103.106.128.0/21, + 103.106.160.0/22, + 103.106.196.0/22, + 103.106.202.0/24, + 103.106.212.0/22, + 103.106.252.0/22, + 103.107.0.0/22, + 103.107.28.0/22, + 103.107.32.0/22, + 103.107.44.0/22, + 103.107.72.0/22, + 103.107.108.0/22, + 103.107.164.0/22, + 103.107.168.0/22, + 103.107.188.0/22, + 103.107.192.0/22, + 103.107.208.0/20, + 103.108.52.0/22, + 103.108.160.0/21, + 103.108.196.0/22, + 103.108.208.0/21, + 103.108.224.0/22, + 103.108.244.0/22, + 103.109.20.0/22, + 103.109.48.0/22, + 103.109.88.0/22, + 103.109.248.0/22, + 103.110.92.0/22, + 103.110.116.0/22, + 103.110.132.0/22, + 103.110.136.0/22, + 103.110.152.0/21, + 103.110.188.0/22, + 103.110.204.0/22, + 103.111.64.0/22, + 103.111.172.0/22, + 103.111.252.0/22, + 103.112.72.0/22, + 103.112.88.0/21, + 103.112.108.0/22, + 103.112.112.0/21, + 103.112.140.0/22, + 103.113.4.0/22, + 103.113.144.0/22, + 103.113.220.0/22, + 103.113.232.0/21, + 103.114.4.0/22, + 103.114.28.0/22, + 103.114.68.0/22, + 103.114.100.0/22, + 103.114.148.0/22, + 103.114.156.0/23, + 103.114.176.0/22, + 103.114.212.0/22, + 103.114.236.0/22, + 103.114.240.0/22, + 103.115.52.0/22, + 103.115.68.0/22, + 103.115.92.0/22, + 103.115.120.0/22, + 103.115.148.0/22, + 103.115.248.0/22, + 103.116.40.0/22, + 103.116.76.0/22, + 103.116.92.0/22, + 103.116.120.0/22, + 103.116.128.0/22, + 103.116.138.0/23, + 103.116.150.0/24, + 103.116.184.0/22, + 103.116.220.0/22, + 103.116.224.0/21, + 103.117.16.0/22, + 103.117.73.0/24, + 103.117.74.0/23, + 103.117.88.0/22, + 103.117.188.0/22, + 103.117.220.0/22, + 103.118.52.0/22, + 103.118.56.0/21, + 103.118.64.0/21, + 103.118.72.0/22, + 103.118.88.0/22, + 103.118.210.0/23, + 103.118.212.0/22, + 103.118.216.0/22, + 103.118.248.0/22, + 103.119.28.0/22, + 103.119.115.0/24, + 103.119.156.0/22, + 103.119.180.0/22, + 103.119.200.0/22, + 103.120.52.0/22, + 103.120.72.0/22, + 103.120.88.0/22, + 103.120.96.0/21, + 103.120.140.0/22, + 103.120.196.0/22, + 103.120.224.0/22, + 103.121.52.0/22, + 103.121.160.0/21, + 103.121.250.0/24, + 103.121.252.0/22, + 103.122.48.0/22, + 103.122.192.0/22, + 103.122.240.0/22, + 103.123.4.0/22, + 103.123.56.0/22, + 103.123.88.0/21, + 103.123.116.0/22, + 103.123.176.0/22, + 103.123.200.0/21, + 103.123.208.0/21, + 103.124.24.0/22, + 103.124.48.0/22, + 103.124.64.0/22, + 103.124.212.0/22, + 103.124.216.0/22, + 103.125.20.0/22, + 103.125.44.0/22, + 103.125.132.0/22, + 103.125.164.0/22, + 103.125.196.0/22, + 103.125.236.0/22, + 103.125.249.0/24, + 103.125.250.0/23, + 103.126.0.0/22, + 103.126.16.0/22, + 103.126.44.0/22, + 103.126.124.0/22, + 103.126.128.0/21, + 103.130.132.0/22, + 103.130.160.0/22, + 103.130.228.0/22, + 103.131.20.0/22, + 103.131.36.0/22, + 103.131.138.0/23, + 103.131.152.0/22, + 103.131.168.0/22, + 103.131.224.0/21, + 103.131.240.0/22, + 103.132.22.0/23, + 103.132.48.0/22, + 103.132.60.0/22, + 103.132.64.0/20, + 103.132.80.0/22, + 103.132.104.0/21, + 103.132.112.0/21, + 103.132.120.0/22, + 103.132.161.0/24, + 103.132.188.0/22, + 103.132.208.0/21, + 103.133.12.0/22, + 103.133.40.0/22, + 103.133.128.0/22, + 103.133.232.0/22, + 103.134.196.0/22, + 103.134.232.0/23, + 103.135.80.0/22, + 103.135.124.0/22, + 103.135.148.0/22, + 103.135.156.0/22, + 103.135.160.0/21, + 103.135.176.0/22, + 103.135.184.0/22, + 103.135.192.0/21, + 103.135.236.0/22, + 103.136.128.0/22, + 103.136.232.0/22, + 103.137.58.0/23, + 103.137.60.0/24, + 103.137.76.0/22, + 103.137.136.0/23, + 103.137.180.0/22, + 103.137.236.0/22, + 103.138.2.0/23, + 103.138.134.0/23, + 103.138.208.0/23, + 103.138.220.0/23, + 103.138.248.0/23, + 103.139.22.0/23, + 103.139.92.0/23, + 103.139.134.0/23, + 103.139.172.0/23, + 103.139.204.0/23, + 103.139.212.0/23, + 103.140.14.0/23, + 103.140.140.0/23, + 103.140.144.0/23, + 103.140.192.0/23, + 103.141.10.0/23, + 103.141.58.0/23, + 103.141.128.0/23, + 103.141.186.0/23, + 103.141.242.0/23, + 103.142.28.0/23, + 103.142.58.0/23, + 103.142.82.0/23, + 103.142.96.0/23, + 103.142.122.0/23, + 103.142.128.0/23, + 103.142.154.0/23, + 103.142.156.0/23, + 103.142.172.0/23, + 103.142.180.0/23, + 103.142.186.0/23, + 103.142.234.0/23, + 103.142.238.0/23, + 103.142.248.0/23, + 103.143.16.0/22, + 103.143.74.0/23, + 103.143.120.0/23, + 103.143.124.0/23, + 103.143.132.0/22, + 103.143.174.0/23, + 103.143.228.0/23, + 103.144.41.0/24, + 103.144.66.0/23, + 103.144.70.0/23, + 103.144.72.0/23, + 103.144.136.0/23, + 103.144.158.0/23, + 103.145.42.0/23, + 103.145.93.0/24, + 103.145.98.0/23, + 103.145.188.0/23, + 103.146.72.0/23, + 103.146.91.0/24, + 103.146.126.0/23, + 103.146.138.0/23, + 103.146.236.0/23, + 103.147.124.0/23, + 103.147.206.0/23, + 103.148.174.0/23, + 103.149.6.0/23, + 103.149.17.0/24, + 103.149.44.0/23, + 103.149.210.0/23, + 103.149.214.0/23, + 103.149.220.0/23, + 103.149.242.0/23, + 103.149.244.0/22, + 103.150.10.0/23, + 103.150.24.0/23, + 103.150.66.0/23, + 103.150.72.0/23, + 103.150.122.0/23, + 103.150.126.0/23, + 103.150.128.0/22, + 103.150.146.0/23, + 103.150.164.0/23, + 103.150.172.0/23, + 103.150.200.0/23, + 103.150.216.0/23, + 103.150.244.0/23, + 103.151.142.0/23, + 103.151.148.0/23, + 103.151.158.0/23, + 103.152.28.0/22, + 103.152.56.0/23, + 103.152.76.0/23, + 103.152.120.0/22, + 103.152.152.0/23, + 103.152.168.0/23, + 103.152.186.0/23, + 103.152.192.0/23, + 103.152.200.0/23, + 103.152.208.0/23, + 103.152.224.0/23, + 103.152.250.0/23, + 103.153.114.0/23, + 103.153.122.0/23, + 103.153.132.0/23, + 103.153.146.0/23, + 103.153.160.0/23, + 103.154.18.0/23, + 103.154.30.0/23, + 103.154.32.0/23, + 103.154.40.0/23, + 103.154.66.0/23, + 103.154.162.0/23, + 103.154.164.0/23, + 103.154.168.0/23, + 103.155.14.0/23, + 103.155.34.0/23, + 103.155.48.0/23, + 103.155.76.0/23, + 103.155.101.0/24, + 103.155.214.0/23, + 103.156.28.0/23, + 103.156.68.0/23, + 103.156.104.0/23, + 103.156.158.0/23, + 103.156.174.0/23, + 103.156.186.0/23, + 103.156.228.0/23, + 103.157.30.0/23, + 103.157.174.0/23, + 103.157.212.0/23, + 103.157.234.0/23, + 103.157.254.0/23, + 103.158.0.0/23, + 103.158.8.0/23, + 103.158.16.0/23, + 103.158.200.0/23, + 103.158.224.0/23, + 103.159.124.0/23, + 103.159.134.0/23, + 103.159.142.0/23, + 103.160.32.0/22, + 103.160.112.0/22, + 103.160.244.0/23, + 103.160.254.0/23, + 103.161.14.0/23, + 103.161.102.0/23, + 103.161.208.0/23, + 103.161.220.0/23, + 103.161.254.0/23, + 103.162.10.0/23, + 103.162.32.0/23, + 103.162.116.0/23, + 103.163.28.0/23, + 103.163.32.0/23, + 103.163.46.0/23, + 103.163.74.0/23, + 103.163.180.0/23, + 103.164.4.0/23, + 103.164.32.0/23, + 103.164.40.0/22, + 103.164.64.0/23, + 103.164.76.0/23, + 103.164.178.0/23, + 103.165.44.0/23, + 103.165.52.0/23, + 103.165.82.0/23, + 103.165.110.0/23, + 103.166.20.0/23, + 103.166.50.0/23, + 103.166.52.0/22, + 103.166.84.0/23, + 103.166.138.0/23, + 103.167.0.0/23, + 103.167.36.0/23, + 103.167.100.0/23, + 103.168.98.0/23, + 103.168.170.0/23, + 103.169.50.0/23, + 103.169.62.0/23, + 103.169.108.0/23, + 103.169.162.0/23, + 103.169.202.0/23, + 103.170.4.0/23, + 103.170.134.0/23, + 103.170.212.0/23, + 103.171.32.0/23, + 103.171.166.0/24, + 103.171.214.0/23, + 103.172.32.0/23, + 103.172.160.0/23, + 103.173.102.0/23, + 103.173.182.0/23, + 103.173.184.0/23, + 103.174.94.0/23, + 103.175.114.0/23, + 103.175.118.0/23, + 103.175.197.0/24, + 103.176.52.0/23, + 103.176.222.0/23, + 103.176.244.0/23, + 103.177.28.0/23, + 103.177.44.0/23, + 103.177.71.0/24, + 103.177.162.0/23, + 103.178.240.0/23, + 103.179.76.0/22, + 103.180.108.0/24, + 103.180.226.0/23, + 103.181.234.0/23, + 103.183.26.0/23, + 103.183.66.0/23, + 103.183.122.0/23, + 103.183.124.0/23, + 103.184.46.0/23, + 103.184.60.0/23, + 103.185.78.0/23, + 103.185.80.0/23, + 103.186.4.0/23, + 103.186.108.0/23, + 103.186.162.0/24, + 103.186.228.0/23, + 103.189.92.0/23, + 103.189.152.0/22, + 103.189.197.0/24, + 103.190.20.0/23, + 103.190.71.0/24, + 103.190.104.0/23, + 103.190.116.0/22, + 103.190.122.0/23, + 103.191.102.0/23, + 103.191.242.0/23, + 103.192.0.0/19, + 103.192.48.0/21, + 103.192.56.0/22, + 103.192.84.0/22, + 103.192.88.0/21, + 103.192.96.0/20, + 103.192.112.0/22, + 103.192.128.0/20, + 103.192.144.0/22, + 103.192.164.0/22, + 103.192.188.0/22, + 103.192.208.0/21, + 103.192.216.0/22, + 103.192.252.0/22, + 103.193.40.0/21, + 103.193.120.0/22, + 103.193.140.0/22, + 103.193.160.0/22, + 103.193.188.0/22, + 103.193.192.0/22, + 103.193.212.0/22, + 103.193.216.0/21, + 103.193.224.0/20, + 103.194.16.0/22, + 103.195.104.0/22, + 103.195.112.0/22, + 103.195.152.0/22, + 103.195.160.0/22, + 103.196.64.0/22, + 103.196.72.0/22, + 103.196.88.0/21, + 103.196.96.0/22, + 103.196.168.0/22, + 103.197.0.0/22, + 103.197.180.0/22, + 103.197.228.0/22, + 103.198.20.0/22, + 103.198.60.0/22, + 103.198.64.0/22, + 103.198.72.0/22, + 103.198.124.0/22, + 103.198.156.0/22, + 103.198.180.0/22, + 103.198.196.0/22, + 103.198.216.0/21, + 103.198.224.0/20, + 103.198.240.0/21, + 103.199.164.0/22, + 103.199.196.0/22, + 103.199.228.0/22, + 103.199.248.0/21, + 103.200.52.0/22, + 103.200.64.0/21, + 103.200.136.0/21, + 103.200.144.0/20, + 103.200.160.0/19, + 103.200.192.0/22, + 103.200.220.0/22, + 103.200.224.0/19, + 103.201.0.0/20, + 103.201.16.0/21, + 103.201.28.0/22, + 103.201.32.0/19, + 103.201.64.0/22, + 103.201.76.0/22, + 103.201.80.0/20, + 103.201.96.0/20, + 103.201.112.0/21, + 103.201.120.0/22, + 103.201.152.0/21, + 103.201.160.0/19, + 103.201.192.0/18, + 103.202.0.0/19, + 103.202.32.0/20, + 103.202.56.0/22, + 103.202.60.0/24, + 103.202.62.0/23, + 103.202.64.0/22, + 103.202.68.0/23, + 103.202.72.0/21, + 103.202.80.0/20, + 103.202.96.0/19, + 103.202.128.0/20, + 103.202.144.0/22, + 103.202.152.0/24, + 103.202.154.0/23, + 103.202.156.0/22, + 103.202.160.0/19, + 103.202.192.0/20, + 103.202.212.0/22, + 103.202.228.0/22, + 103.202.236.0/22, + 103.202.240.0/20, + 103.203.0.0/19, + 103.203.32.0/22, + 103.203.96.0/19, + 103.203.128.0/22, + 103.203.140.0/22, + 103.203.164.0/22, + 103.203.168.0/22, + 103.203.192.0/22, + 103.203.200.0/22, + 103.203.212.0/22, + 103.203.216.0/22, + 103.204.24.0/22, + 103.204.72.0/22, + 103.204.88.0/22, + 103.204.112.0/22, + 103.204.136.0/21, + 103.204.144.0/21, + 103.204.152.0/22, + 103.204.196.0/22, + 103.204.216.0/23, + 103.204.232.0/21, + 103.205.4.0/22, + 103.205.40.0/21, + 103.205.52.0/22, + 103.205.108.0/22, + 103.205.116.0/22, + 103.205.136.0/22, + 103.205.162.0/24, + 103.205.188.0/22, + 103.205.192.0/21, + 103.205.200.0/22, + 103.205.236.0/22, + 103.205.248.0/21, + 103.206.0.0/22, + 103.206.44.0/22, + 103.206.148.0/22, + 103.207.104.0/22, + 103.207.184.0/21, + 103.207.192.0/20, + 103.207.208.0/21, + 103.207.220.0/22, + 103.207.228.0/22, + 103.207.232.0/22, + 103.208.12.0/22, + 103.208.16.0/22, + 103.208.28.0/22, + 103.208.40.0/21, + 103.208.48.0/22, + 103.209.112.0/22, + 103.209.136.0/22, + 103.209.200.0/22, + 103.209.208.0/22, + 103.209.216.0/22, + 103.210.96.0/22, + 103.210.156.0/22, + 103.210.160.0/19, + 103.210.216.0/22, + 103.211.44.0/22, + 103.211.96.0/22, + 103.211.156.0/22, + 103.211.164.0/22, + 103.211.220.0/22, + 103.211.224.0/22, + 103.211.248.0/22, + 103.212.0.0/20, + 103.212.44.0/22, + 103.212.48.0/22, + 103.212.84.0/22, + 103.212.100.0/22, + 103.212.108.0/22, + 103.212.148.0/22, + 103.212.164.0/22, + 103.212.196.0/22, + 103.212.200.0/22, + 103.212.252.0/22, + 103.213.40.0/21, + 103.213.48.0/20, + 103.213.64.0/19, + 103.213.96.0/22, + 103.213.132.0/22, + 103.213.136.0/21, + 103.213.144.0/20, + 103.213.160.0/19, + 103.213.196.0/22, + 103.213.226.0/23, + 103.213.232.0/23, + 103.214.48.0/22, + 103.214.84.0/22, + 103.214.212.0/22, + 103.214.240.0/21, + 103.215.28.0/22, + 103.215.32.0/21, + 103.215.44.0/22, + 103.215.100.0/22, + 103.215.108.0/22, + 103.215.116.0/22, + 103.215.120.0/22, + 103.215.140.0/22, + 103.216.4.0/22, + 103.216.8.0/21, + 103.216.16.0/20, + 103.216.32.0/20, + 103.216.64.0/22, + 103.216.108.0/22, + 103.216.136.0/22, + 103.216.152.0/22, + 103.216.156.0/23, + 103.216.224.0/22, + 103.216.240.0/20, + 103.217.0.0/18, + 103.217.168.0/22, + 103.217.180.0/22, + 103.217.184.0/21, + 103.217.192.0/20, + 103.218.8.0/21, + 103.218.16.0/21, + 103.218.28.0/22, + 103.218.32.0/19, + 103.218.64.0/20, + 103.218.80.0/21, + 103.218.88.0/22, + 103.218.178.0/23, + 103.218.192.0/20, + 103.218.208.0/21, + 103.218.216.0/22, + 103.219.24.0/21, + 103.219.32.0/21, + 103.219.64.0/22, + 103.219.84.0/22, + 103.219.88.0/21, + 103.219.96.0/21, + 103.219.176.0/22, + 103.219.184.0/22, + 103.220.48.0/20, + 103.220.64.0/22, + 103.220.92.0/22, + 103.220.96.0/20, + 103.220.116.0/22, + 103.220.120.0/21, + 103.220.128.0/20, + 103.220.144.0/21, + 103.220.152.0/22, + 103.220.160.0/19, + 103.220.192.0/21, + 103.220.200.0/22, + 103.220.240.0/21, + 103.221.88.0/22, + 103.221.96.0/19, + 103.221.128.0/18, + 103.221.192.0/20, + 103.222.0.0/20, + 103.222.16.0/22, + 103.222.24.0/21, + 103.222.32.0/19, + 103.222.64.0/18, + 103.222.128.0/18, + 103.222.192.0/19, + 103.222.224.0/21, + 103.222.232.0/22, + 103.222.240.0/21, + 103.223.16.0/20, + 103.223.32.0/19, + 103.223.64.0/19, + 103.223.96.0/20, + 103.223.112.0/21, + 103.223.124.0/22, + 103.223.128.0/21, + 103.223.140.0/22, + 103.223.144.0/20, + 103.223.160.0/20, + 103.223.176.0/21, + 103.223.188.0/22, + 103.223.192.0/18, + 103.224.0.0/22, + 103.224.40.0/21, + 103.224.60.0/22, + 103.224.220.0/22, + 103.224.224.0/21, + 103.224.232.0/22, + 103.225.18.0/24, + 103.226.16.0/22, + 103.226.40.0/22, + 103.226.57.0/24, + 103.226.58.0/23, + 103.226.60.0/22, + 103.226.80.0/22, + 103.226.116.0/23, + 103.226.132.0/22, + 103.226.156.0/22, + 103.226.180.0/22, + 103.226.196.0/22, + 103.227.48.0/22, + 103.227.72.0/21, + 103.227.80.0/22, + 103.227.100.0/22, + 103.227.120.0/22, + 103.227.132.0/22, + 103.227.136.0/22, + 103.227.196.0/22, + 103.227.204.0/22, + 103.227.212.0/22, + 103.227.228.0/22, + 103.228.12.0/22, + 103.228.88.0/22, + 103.228.136.0/22, + 103.228.160.0/22, + 103.228.176.0/22, + 103.228.204.0/22, + 103.228.208.0/22, + 103.228.228.0/22, + 103.228.232.0/22, + 103.229.20.0/22, + 103.229.136.0/22, + 103.229.148.0/22, + 103.229.172.0/22, + 103.229.212.0/22, + 103.229.216.0/21, + 103.229.228.0/22, + 103.229.236.0/22, + 103.229.240.0/22, + 103.230.0.0/22, + 103.230.28.0/22, + 103.230.44.0/22, + 103.230.96.0/22, + 103.230.110.0/23, + 103.230.196.0/22, + 103.230.200.0/21, + 103.230.212.0/22, + 103.230.236.0/22, + 103.231.16.0/21, + 103.231.64.0/21, + 103.231.180.0/22, + 103.231.185.0/24, + 103.231.186.0/23, + 103.231.244.0/22, + 103.232.4.0/22, + 103.232.144.0/22, + 103.232.166.0/23, + 103.232.188.0/22, + 103.233.4.0/22, + 103.233.44.0/22, + 103.233.52.0/22, + 103.233.104.0/22, + 103.233.128.0/22, + 103.233.136.0/22, + 103.233.162.0/23, + 103.233.178.0/24, + 103.233.228.0/22, + 103.234.0.0/22, + 103.234.20.0/22, + 103.234.56.0/22, + 103.234.128.0/22, + 103.234.172.0/22, + 103.234.180.0/22, + 103.234.244.0/22, + 103.235.48.0/22, + 103.235.56.0/21, + 103.235.80.0/22, + 103.235.85.0/24, + 103.235.87.0/24, + 103.235.100.0/22, + 103.235.128.0/20, + 103.235.144.0/21, + 103.235.184.0/22, + 103.235.192.0/22, + 103.235.200.0/22, + 103.235.220.0/22, + 103.235.224.0/19, + 103.236.0.0/18, + 103.236.64.0/19, + 103.236.96.0/22, + 103.236.116.0/23, + 103.236.120.0/22, + 103.236.184.0/22, + 103.236.220.0/22, + 103.236.232.0/22, + 103.236.240.0/21, + 103.236.248.0/23, + 103.236.250.0/24, + 103.236.252.0/22, + 103.237.0.0/20, + 103.237.24.0/21, + 103.237.68.0/22, + 103.237.88.0/22, + 103.237.152.0/22, + 103.237.176.0/20, + 103.237.192.0/18, + 103.238.0.0/21, + 103.238.18.0/23, + 103.238.20.0/22, + 103.238.24.0/21, + 103.238.32.0/21, + 103.238.40.0/22, + 103.238.46.0/23, + 103.238.48.0/21, + 103.238.56.0/22, + 103.238.88.0/21, + 103.238.96.0/22, + 103.238.132.0/22, + 103.238.140.0/22, + 103.238.144.0/22, + 103.238.152.0/23, + 103.238.160.0/19, + 103.238.196.0/22, + 103.238.204.0/22, + 103.238.252.0/22, + 103.239.44.0/22, + 103.239.68.0/22, + 103.239.152.0/21, + 103.239.180.0/22, + 103.239.184.0/22, + 103.239.192.0/21, + 103.239.204.0/22, + 103.239.208.0/22, + 103.239.224.0/22, + 103.239.244.0/22, + 103.240.16.0/22, + 103.240.36.0/22, + 103.240.42.0/23, + 103.240.72.0/22, + 103.240.84.0/22, + 103.240.124.0/22, + 103.240.172.0/22, + 103.240.188.0/22, + 103.240.201.0/24, + 103.240.202.0/23, + 103.240.244.0/22, + 103.241.12.0/22, + 103.241.92.0/22, + 103.241.96.0/22, + 103.241.160.0/22, + 103.241.172.0/23, + 103.241.184.0/21, + 103.241.220.0/22, + 103.242.64.0/22, + 103.242.128.0/22, + 103.242.160.0/22, + 103.242.168.0/21, + 103.242.176.0/22, + 103.242.200.0/22, + 103.242.212.0/22, + 103.242.220.0/22, + 103.242.240.0/22, + 103.243.136.0/22, + 103.243.252.0/22, + 103.244.16.0/22, + 103.244.26.0/23, + 103.244.58.0/23, + 103.244.60.0/22, + 103.244.64.0/20, + 103.244.80.0/21, + 103.244.119.0/24, + 103.244.164.0/22, + 103.244.232.0/22, + 103.244.252.0/22, + 103.245.23.0/24, + 103.245.52.0/22, + 103.245.60.0/22, + 103.245.80.0/22, + 103.245.124.0/22, + 103.245.128.0/22, + 103.246.8.0/21, + 103.246.120.0/21, + 103.246.132.0/22, + 103.246.152.0/22, + 103.247.168.0/21, + 103.247.176.0/22, + 103.247.191.0/24, + 103.247.200.0/22, + 103.247.212.0/22, + 103.248.0.0/23, + 103.248.64.0/22, + 103.248.100.0/22, + 103.248.124.0/22, + 103.248.152.0/22, + 103.248.168.0/22, + 103.248.192.0/22, + 103.248.212.0/22, + 103.248.224.0/22, + 103.249.8.0/21, + 103.249.52.0/22, + 103.249.128.0/22, + 103.249.136.0/22, + 103.249.144.0/22, + 103.249.164.0/22, + 103.249.168.0/21, + 103.249.176.0/22, + 103.249.188.0/22, + 103.249.192.0/22, + 103.249.244.0/22, + 103.249.252.0/22, + 103.250.32.0/22, + 103.250.104.0/22, + 103.250.124.0/22, + 103.250.180.0/22, + 103.250.192.0/22, + 103.250.216.0/22, + 103.250.224.0/22, + 103.250.236.0/22, + 103.250.248.0/21, + 103.251.32.0/22, + 103.251.84.0/22, + 103.251.96.0/22, + 103.251.124.0/22, + 103.251.129.0/24, + 103.251.131.0/24, + 103.251.160.0/22, + 103.251.192.0/22, + 103.251.204.0/22, + 103.251.240.0/22, + 103.252.28.0/22, + 103.252.36.0/22, + 103.252.64.0/22, + 103.252.96.0/22, + 103.252.104.0/22, + 103.252.172.0/22, + 103.252.204.0/22, + 103.252.208.0/22, + 103.252.232.0/22, + 103.252.248.0/22, + 103.253.4.0/22, + 103.253.60.0/22, + 103.253.204.0/22, + 103.253.220.0/22, + 103.253.224.0/22, + 103.253.232.0/22, + 103.254.8.0/22, + 103.254.20.0/22, + 103.254.64.0/21, + 103.254.76.0/22, + 103.254.112.0/22, + 103.254.176.0/22, + 103.254.188.0/22, + 103.255.68.0/22, + 103.255.88.0/21, + 103.255.136.0/21, + 103.255.184.0/22, + 103.255.200.0/22, + 103.255.212.0/22, + 103.255.228.0/22, + 104.192.108.0/23, + 104.192.110.0/24, + 106.0.0.0/24, + 106.0.2.0/23, + 106.0.4.0/22, + 106.0.8.0/21, + 106.0.16.0/20, + 106.0.44.0/22, + 106.0.64.0/18, + 106.2.0.0/17, + 106.2.128.0/20, + 106.2.144.0/21, + 106.2.152.0/22, + 106.2.156.0/23, + 106.2.160.0/19, + 106.2.192.0/18, + 106.3.0.0/18, + 106.3.64.0/20, + 106.3.80.0/22, + 106.3.88.0/21, + 106.3.96.0/19, + 106.3.128.0/17, + 106.4.0.0/14, + 106.8.0.0/15, + 106.11.0.0/16, + 106.12.0.0/14, + 106.16.0.0/12, + 106.32.0.0/12, + 106.48.64.0/18, + 106.50.0.0/16, + 106.52.0.0/14, + 106.56.0.0/13, + 106.74.0.0/16, + 106.75.0.0/17, + 106.75.128.0/18, + 106.75.200.0/21, + 106.75.208.0/20, + 106.75.224.0/19, + 106.80.0.0/12, + 106.108.0.0/14, + 106.112.0.0/12, + 106.224.0.0/12, + 109.244.0.0/16, + 110.6.0.0/15, + 110.16.0.0/14, + 110.34.40.0/21, + 110.40.0.0/14, + 110.44.12.0/22, + 110.44.144.0/20, + 110.48.0.0/16, + 110.51.0.0/16, + 110.52.0.0/15, + 110.56.0.0/13, + 110.64.0.0/15, + 110.72.0.0/15, + 110.75.0.0/16, + 110.76.0.0/18, + 110.76.132.0/22, + 110.76.156.0/22, + 110.76.184.0/22, + 110.76.192.0/18, + 110.77.0.0/17, + 110.80.0.0/13, + 110.88.0.0/14, + 110.92.68.0/22, + 110.93.32.0/19, + 110.94.0.0/15, + 110.96.0.0/11, + 110.152.0.0/14, + 110.156.0.0/15, + 110.165.32.0/19, + 110.166.0.0/15, + 110.172.192.0/18, + 110.173.0.0/19, + 110.173.32.0/20, + 110.173.64.0/18, + 110.173.192.0/19, + 110.176.0.0/12, + 110.192.0.0/11, + 110.228.0.0/14, + 110.232.32.0/19, + 110.236.0.0/15, + 110.240.0.0/12, + 111.0.0.0/10, + 111.66.0.0/21, + 111.66.12.0/22, + 111.66.16.0/22, + 111.66.28.0/22, + 111.66.32.0/21, + 111.66.44.0/22, + 111.66.52.0/22, + 111.66.60.0/22, + 111.66.64.0/18, + 111.66.128.0/19, + 111.66.160.0/20, + 111.66.176.0/23, + 111.66.178.0/24, + 111.66.180.0/22, + 111.66.184.0/21, + 111.67.192.0/20, + 111.68.64.0/19, + 111.72.0.0/13, + 111.85.0.0/16, + 111.92.248.0/21, + 111.112.0.0/14, + 111.116.0.0/15, + 111.118.200.0/21, + 111.119.64.0/18, + 111.119.128.0/19, + 111.120.0.0/14, + 111.124.0.0/16, + 111.126.0.0/15, + 111.128.0.0/11, + 111.160.0.0/13, + 111.170.0.0/16, + 111.172.0.0/14, + 111.176.0.0/13, + 111.186.0.0/15, + 111.192.0.0/12, + 111.208.0.0/13, + 111.221.28.0/24, + 111.221.128.0/17, + 111.222.0.0/16, + 111.223.4.0/22, + 111.223.12.0/22, + 111.223.16.0/22, + 111.223.240.0/22, + 111.223.248.0/22, + 111.224.0.0/13, + 111.235.96.0/19, + 111.235.156.0/22, + 111.235.160.0/19, + 112.0.0.0/10, + 112.64.0.0/14, + 112.73.222.0/24, + 112.74.0.0/15, + 112.80.0.0/12, + 112.96.0.0/13, + 112.109.128.0/17, + 112.111.0.0/16, + 112.112.0.0/14, + 112.116.0.0/15, + 112.122.0.0/15, + 112.124.0.0/14, + 112.128.0.0/14, + 112.132.0.0/16, + 112.137.48.0/21, + 112.192.0.0/14, + 112.224.0.0/11, + 113.0.0.0/13, + 113.8.0.0/15, + 113.11.192.0/19, + 113.12.0.0/14, + 113.16.0.0/15, + 113.18.0.0/16, + 113.21.232.0/21, + 113.24.0.0/14, + 113.31.0.0/16, + 113.44.0.0/14, + 113.48.0.0/14, + 113.52.160.0/19, + 113.52.228.0/22, + 113.54.0.0/15, + 113.56.0.0/15, + 113.58.0.0/16, + 113.59.0.0/17, + 113.59.224.0/22, + 113.62.0.0/15, + 113.64.0.0/10, + 113.128.0.0/15, + 113.130.96.0/20, + 113.130.112.0/21, + 113.132.0.0/14, + 113.136.0.0/13, + 113.192.40.0/23, + 113.194.0.0/15, + 113.197.100.0/22, + 113.200.0.0/15, + 113.202.0.0/16, + 113.204.0.0/14, + 113.208.96.0/19, + 113.208.128.0/17, + 113.209.0.0/16, + 113.212.0.0/18, + 113.212.100.0/22, + 113.212.184.0/21, + 113.213.0.0/17, + 113.214.0.0/15, + 113.218.0.0/15, + 113.220.0.0/14, + 113.224.0.0/12, + 113.240.0.0/13, + 113.248.0.0/14, + 114.28.0.0/17, + 114.28.128.0/18, + 114.28.193.0/24, + 114.28.194.0/24, + 114.28.197.0/24, + 114.28.198.0/23, + 114.28.201.0/24, + 114.28.203.0/24, + 114.28.205.0/24, + 114.28.206.0/23, + 114.28.208.0/23, + 114.28.210.0/24, + 114.28.216.0/23, + 114.28.220.0/22, + 114.28.225.0/24, + 114.28.230.0/24, + 114.28.232.0/24, + 114.28.235.0/24, + 114.28.236.0/23, + 114.28.239.0/24, + 114.28.240.0/20, + 114.31.64.0/21, + 114.54.0.0/15, + 114.60.0.0/14, + 114.64.0.0/14, + 114.68.0.0/16, + 114.79.64.0/18, + 114.80.0.0/12, + 114.96.0.0/13, + 114.104.0.0/14, + 114.110.0.0/20, + 114.110.64.0/18, + 114.111.0.0/19, + 114.111.160.0/20, + 114.111.176.0/22, + 114.111.190.0/23, + 114.112.4.0/22, + 114.112.8.0/22, + 114.112.12.0/23, + 114.112.16.0/23, + 114.112.20.0/22, + 114.112.24.0/21, + 114.112.32.0/19, + 114.112.64.0/18, + 114.112.128.0/18, + 114.112.192.0/19, + 114.112.224.0/22, + 114.112.228.0/24, + 114.112.230.0/23, + 114.112.234.0/23, + 114.112.240.0/23, + 114.112.245.0/24, + 114.112.247.0/24, + 114.112.248.0/23, + 114.112.251.0/24, + 114.112.252.0/23, + 114.113.0.0/17, + 114.113.128.0/21, + 114.113.140.0/22, + 114.113.144.0/20, + 114.113.160.0/19, + 114.113.192.0/19, + 114.113.224.0/20, + 114.113.242.0/24, + 114.113.246.0/23, + 114.113.248.0/21, + 114.114.0.0/15, + 114.116.0.0/15, + 114.118.0.0/16, + 114.119.0.0/17, + 114.119.192.0/21, + 114.119.204.0/22, + 114.119.208.0/20, + 114.119.224.0/19, + 114.132.0.0/16, + 114.135.0.0/16, + 114.138.0.0/15, + 114.141.64.0/21, + 114.141.80.0/21, + 114.141.128.0/18, + 114.142.136.0/21, + 114.196.0.0/15, + 114.198.248.0/21, + 114.208.0.0/12, + 114.224.0.0/11, + 115.24.0.0/14, + 115.28.0.0/15, + 115.31.64.0/20, + 115.32.0.0/14, + 115.42.56.0/22, + 115.44.0.0/14, + 115.48.0.0/12, + 115.69.64.0/20, + 115.84.0.0/18, + 115.85.192.0/18, + 115.100.0.0/14, + 115.104.0.0/14, + 115.120.0.0/14, + 115.124.16.0/20, + 115.148.0.0/14, + 115.152.0.0/13, + 115.166.64.0/19, + 115.168.0.0/13, + 115.180.0.0/14, + 115.187.0.0/20, + 115.190.0.0/15, + 115.192.0.0/11, + 115.224.0.0/12, + 116.0.8.0/21, + 116.0.24.0/21, + 116.1.0.0/16, + 116.2.0.0/15, + 116.4.0.0/14, + 116.8.0.0/14, + 116.13.0.0/16, + 116.16.0.0/12, + 116.50.0.0/20, + 116.52.0.0/14, + 116.56.0.0/15, + 116.58.128.0/20, + 116.58.208.0/20, + 116.60.0.0/14, + 116.66.0.0/17, + 116.68.136.0/21, + 116.68.176.0/21, + 116.69.0.0/16, + 116.70.0.0/17, + 116.76.0.0/14, + 116.85.0.0/16, + 116.89.144.0/20, + 116.90.80.0/20, + 116.90.184.0/21, + 116.95.0.0/16, + 116.112.0.0/14, + 116.116.0.0/15, + 116.128.0.0/10, + 116.192.0.0/16, + 116.193.16.0/20, + 116.193.32.0/19, + 116.193.176.0/21, + 116.194.0.0/15, + 116.196.0.0/21, + 116.196.8.0/22, + 116.196.12.0/23, + 116.196.14.0/24, + 116.196.16.0/20, + 116.196.32.0/19, + 116.196.64.0/18, + 116.196.128.0/17, + 116.197.160.0/21, + 116.198.0.0/16, + 116.199.0.0/17, + 116.199.128.0/20, + 116.199.144.0/21, + 116.204.0.0/17, + 116.205.0.0/16, + 116.207.0.0/16, + 116.208.0.0/14, + 116.212.160.0/20, + 116.213.44.0/24, + 116.213.46.0/23, + 116.213.64.0/18, + 116.213.128.0/17, + 116.214.32.0/19, + 116.214.64.0/20, + 116.214.128.0/17, + 116.215.0.0/16, + 116.216.0.0/14, + 116.224.0.0/12, + 116.242.0.0/15, + 116.244.0.0/14, + 116.248.0.0/15, + 116.251.64.0/18, + 116.252.0.0/15, + 116.254.104.0/21, + 116.254.128.0/17, + 116.255.128.0/17, + 117.8.0.0/13, + 117.21.0.0/16, + 117.22.0.0/15, + 117.24.0.0/13, + 117.32.0.0/13, + 117.40.0.0/14, + 117.44.0.0/15, + 117.48.0.0/15, + 117.50.0.0/16, + 117.51.128.0/17, + 117.53.48.0/20, + 117.53.176.0/20, + 117.57.0.0/16, + 117.58.0.0/17, + 117.59.0.0/16, + 117.60.0.0/14, + 117.64.0.0/13, + 117.72.0.0/15, + 117.74.64.0/19, + 117.74.128.0/17, + 117.75.0.0/16, + 117.76.0.0/14, + 117.80.0.0/12, + 117.100.0.0/15, + 117.103.16.0/20, + 117.103.40.0/21, + 117.103.72.0/21, + 117.103.128.0/20, + 117.104.168.0/21, + 117.106.0.0/15, + 117.112.0.0/13, + 117.120.64.0/18, + 117.120.148.0/22, + 117.120.164.0/22, + 117.120.184.0/21, + 117.120.196.0/22, + 117.120.208.0/21, + 117.120.216.0/22, + 117.120.248.0/21, + 117.121.0.0/17, + 117.121.128.0/18, + 117.121.192.0/21, + 117.122.128.0/17, + 117.124.0.0/14, + 117.128.0.0/14, + 117.132.0.0/15, + 117.134.0.0/17, + 117.134.128.0/18, + 117.134.208.0/23, + 117.134.212.0/23, + 117.134.216.0/22, + 117.134.222.0/23, + 117.134.232.0/21, + 117.134.240.0/20, + 117.135.0.0/16, + 117.136.0.0/13, + 117.144.0.0/12, + 117.160.0.0/11, + 118.24.0.0/15, + 118.26.0.0/19, + 118.26.32.0/22, + 118.26.40.0/21, + 118.26.48.0/21, + 118.26.64.0/19, + 118.26.96.0/21, + 118.26.112.0/20, + 118.26.128.0/22, + 118.26.133.0/24, + 118.26.134.0/23, + 118.26.136.0/21, + 118.26.160.0/20, + 118.26.188.0/22, + 118.26.192.0/18, + 118.28.0.0/14, + 118.64.0.0/15, + 118.66.0.0/16, + 118.67.112.0/20, + 118.72.0.0/13, + 118.80.0.0/15, + 118.84.0.0/15, + 118.88.32.0/19, + 118.88.64.0/18, + 118.88.128.0/17, + 118.89.0.0/16, + 118.91.240.0/20, + 118.102.16.0/20, + 118.102.32.0/21, + 118.103.164.0/22, + 118.103.168.0/21, + 118.103.176.0/22, + 118.112.0.0/13, + 118.120.0.0/14, + 118.124.0.0/15, + 118.126.0.0/16, + 118.127.128.0/19, + 118.132.0.0/14, + 118.144.0.0/14, + 118.178.0.0/16, + 118.180.0.0/14, + 118.184.0.0/16, + 118.186.0.0/15, + 118.188.0.0/16, + 118.190.0.0/15, + 118.192.0.0/16, + 118.193.0.0/20, + 118.193.48.0/21, + 118.193.96.0/19, + 118.193.128.0/17, + 118.194.0.0/17, + 118.194.128.0/18, + 118.194.192.0/19, + 118.194.227.0/24, + 118.194.240.0/21, + 118.195.0.0/16, + 118.196.0.0/14, + 118.202.0.0/15, + 118.204.0.0/14, + 118.212.0.0/15, + 118.215.192.0/18, + 118.224.0.0/14, + 118.228.0.0/15, + 118.230.0.0/16, + 118.239.0.0/16, + 118.242.0.0/16, + 118.244.0.0/14, + 118.248.0.0/13, + 119.0.0.0/15, + 119.2.0.0/19, + 119.2.128.0/17, + 119.3.0.0/16, + 119.4.0.0/14, + 119.10.0.0/17, + 119.15.136.0/21, + 119.16.0.0/16, + 119.18.192.0/20, + 119.18.208.0/21, + 119.18.224.0/19, + 119.19.0.0/16, + 119.20.0.0/14, + 119.27.64.0/18, + 119.27.128.0/17, + 119.28.28.0/24, + 119.28.240.0/20, + 119.29.0.0/16, + 119.30.48.0/20, + 119.31.192.0/19, + 119.32.0.0/13, + 119.40.0.0/18, + 119.40.64.0/20, + 119.40.128.0/17, + 119.41.0.0/16, + 119.42.0.0/19, + 119.42.128.0/20, + 119.42.224.0/19, + 119.44.0.0/15, + 119.48.0.0/13, + 119.57.0.0/16, + 119.58.0.0/16, + 119.59.128.0/17, + 119.60.0.0/15, + 119.62.0.0/16, + 119.63.32.0/19, + 119.75.208.0/20, + 119.78.0.0/15, + 119.80.0.0/16, + 119.82.208.0/20, + 119.84.0.0/14, + 119.88.0.0/14, + 119.96.0.0/13, + 119.108.0.0/15, + 119.112.0.0/12, + 119.128.0.0/12, + 119.144.0.0/14, + 119.148.160.0/19, + 119.151.192.0/18, + 119.160.200.0/21, + 119.161.120.0/21, + 119.161.128.0/21, + 119.161.144.0/21, + 119.161.160.0/19, + 119.161.192.0/18, + 119.162.0.0/15, + 119.164.0.0/14, + 119.176.0.0/12, + 119.232.0.0/15, + 119.235.128.0/18, + 119.248.0.0/14, + 119.252.96.0/21, + 119.252.240.0/21, + 119.252.248.0/23, + 119.252.250.0/24, + 119.252.252.0/23, + 119.252.255.0/24, + 119.253.0.0/16, + 119.254.0.0/15, + 120.0.0.0/12, + 120.24.0.0/14, + 120.30.0.0/15, + 120.32.0.0/12, + 120.48.0.0/15, + 120.52.0.0/14, + 120.64.0.0/13, + 120.72.32.0/19, + 120.72.128.0/17, + 120.76.0.0/14, + 120.80.0.0/13, + 120.88.8.0/21, + 120.90.0.0/15, + 120.92.0.0/16, + 120.94.0.0/15, + 120.128.0.0/13, + 120.136.16.0/21, + 120.136.128.0/18, + 120.137.0.0/17, + 120.143.128.0/19, + 120.192.0.0/10, + 121.0.8.0/21, + 121.0.16.0/20, + 121.4.0.0/15, + 121.8.0.0/13, + 121.16.0.0/12, + 121.32.0.0/13, + 121.40.0.0/14, + 121.46.0.0/18, + 121.46.76.0/22, + 121.46.128.0/21, + 121.46.141.0/24, + 121.46.160.0/19, + 121.46.192.0/18, + 121.47.0.0/16, + 121.48.0.0/15, + 121.50.8.0/21, + 121.51.0.0/16, + 121.52.160.0/19, + 121.52.208.0/20, + 121.52.224.0/19, + 121.54.176.0/21, + 121.55.0.0/18, + 121.56.0.0/15, + 121.58.0.0/17, + 121.58.136.0/21, + 121.58.144.0/20, + 121.58.160.0/21, + 121.59.0.0/19, + 121.59.32.0/22, + 121.59.36.0/23, + 121.59.39.0/24, + 121.59.40.0/21, + 121.59.48.0/20, + 121.59.64.0/19, + 121.59.96.0/22, + 121.59.101.0/24, + 121.59.102.0/23, + 121.59.104.0/23, + 121.59.107.0/24, + 121.59.108.0/22, + 121.59.112.0/21, + 121.59.121.0/24, + 121.59.123.0/24, + 121.59.125.0/24, + 121.59.126.0/23, + 121.59.128.0/21, + 121.59.136.0/22, + 121.59.141.0/24, + 121.59.142.0/23, + 121.59.144.0/20, + 121.59.160.0/19, + 121.59.192.0/18, + 121.60.0.0/14, + 121.68.0.0/14, + 121.76.0.0/15, + 121.79.128.0/18, + 121.89.0.0/16, + 121.91.104.0/21, + 121.100.128.0/17, + 121.101.0.0/18, + 121.101.208.0/20, + 121.192.0.0/13, + 121.200.192.0/21, + 121.201.0.0/16, + 121.204.0.0/14, + 121.224.0.0/12, + 121.248.0.0/14, + 121.255.0.0/16, + 122.0.64.0/18, + 122.0.128.0/17, + 122.4.0.0/14, + 122.8.52.0/22, + 122.8.57.0/24, + 122.8.58.0/23, + 122.8.60.0/23, + 122.8.63.0/24, + 122.8.68.0/22, + 122.8.76.0/22, + 122.8.80.0/23, + 122.8.116.0/22, + 122.8.192.0/18, + 122.9.0.0/16, + 122.10.129.0/24, + 122.10.131.0/24, + 122.10.132.0/23, + 122.10.136.0/23, + 122.10.164.0/22, + 122.10.168.0/21, + 122.10.176.0/20, + 122.10.192.0/22, + 122.10.200.0/22, + 122.10.205.0/24, + 122.10.206.0/23, + 122.10.208.0/21, + 122.10.216.0/22, + 122.10.228.0/22, + 122.10.232.0/21, + 122.10.240.0/22, + 122.11.0.0/17, + 122.12.0.0/15, + 122.14.0.0/17, + 122.14.128.0/20, + 122.14.144.0/22, + 122.14.148.0/24, + 122.14.150.0/23, + 122.14.152.0/23, + 122.14.155.0/24, + 122.14.157.0/24, + 122.14.158.0/23, + 122.14.160.0/19, + 122.14.192.0/18, + 122.48.0.0/16, + 122.49.0.0/18, + 122.51.0.0/16, + 122.64.0.0/11, + 122.96.0.0/15, + 122.102.0.0/20, + 122.102.64.0/19, + 122.112.0.0/18, + 122.112.64.0/19, + 122.112.96.0/20, + 122.112.112.0/21, + 122.112.120.0/23, + 122.112.125.0/24, + 122.112.126.0/23, + 122.112.128.0/17, + 122.113.0.0/19, + 122.113.32.0/20, + 122.113.48.0/23, + 122.113.51.0/24, + 122.113.52.0/22, + 122.113.56.0/21, + 122.113.64.0/18, + 122.113.128.0/17, + 122.114.0.0/16, + 122.115.0.0/18, + 122.115.64.0/22, + 122.115.70.0/23, + 122.115.72.0/24, + 122.115.74.0/23, + 122.115.76.0/22, + 122.115.80.0/20, + 122.115.96.0/19, + 122.115.128.0/17, + 122.119.0.0/16, + 122.128.100.0/22, + 122.128.120.0/21, + 122.136.0.0/13, + 122.144.128.0/17, + 122.152.192.0/18, + 122.156.0.0/14, + 122.188.0.0/14, + 122.192.0.0/14, + 122.198.0.0/16, + 122.200.40.0/21, + 122.200.64.0/18, + 122.201.48.0/20, + 122.204.0.0/14, + 122.224.0.0/12, + 122.240.0.0/13, + 122.248.24.0/21, + 122.248.48.0/20, + 122.255.64.0/21, + 123.0.128.0/18, + 123.4.0.0/14, + 123.8.0.0/13, + 123.49.128.0/18, + 123.49.192.0/23, + 123.49.194.0/24, + 123.49.196.0/22, + 123.49.200.0/21, + 123.49.208.0/20, + 123.49.224.0/20, + 123.49.240.0/24, + 123.49.242.0/23, + 123.49.245.0/24, + 123.49.248.0/21, + 123.50.160.0/19, + 123.52.0.0/14, + 123.56.0.0/15, + 123.58.0.0/17, + 123.58.128.0/18, + 123.58.224.0/19, + 123.59.0.0/16, + 123.60.0.0/15, + 123.62.0.0/16, + 123.64.0.0/11, + 123.96.0.0/15, + 123.98.0.0/21, + 123.98.12.0/22, + 123.98.24.0/21, + 123.98.32.0/19, + 123.98.64.0/18, + 123.99.128.0/19, + 123.99.160.0/22, + 123.99.164.0/24, + 123.99.166.0/23, + 123.99.168.0/21, + 123.99.176.0/20, + 123.99.192.0/18, + 123.100.0.0/19, + 123.101.0.0/16, + 123.103.0.0/17, + 123.108.88.0/23, + 123.108.130.0/23, + 123.108.132.0/22, + 123.108.136.0/21, + 123.108.208.0/20, + 123.112.0.0/12, + 123.128.0.0/13, + 123.136.88.0/22, + 123.137.0.0/16, + 123.138.0.0/15, + 123.144.0.0/12, + 123.160.0.0/12, + 123.176.60.0/22, + 123.176.80.0/20, + 123.177.0.0/16, + 123.178.0.0/15, + 123.180.0.0/14, + 123.184.0.0/13, + 123.196.0.0/15, + 123.199.128.0/17, + 123.206.0.0/15, + 123.232.0.0/14, + 123.242.0.0/17, + 123.242.192.0/21, + 123.244.0.0/14, + 123.249.0.0/16, + 123.253.52.0/23, + 123.254.96.0/21, + 124.6.64.0/18, + 124.14.0.0/15, + 124.16.0.0/15, + 124.20.0.0/16, + 124.21.32.0/19, + 124.21.64.0/18, + 124.21.128.0/17, + 124.22.0.0/15, + 124.28.192.0/18, + 124.29.0.0/17, + 124.31.0.0/16, + 124.40.112.0/20, + 124.40.128.0/18, + 124.40.192.0/19, + 124.40.240.0/22, + 124.42.0.0/16, + 124.47.0.0/18, + 124.64.0.0/15, + 124.66.0.0/17, + 124.67.0.0/16, + 124.68.16.0/20, + 124.68.64.0/18, + 124.69.0.0/16, + 124.70.0.0/16, + 124.71.0.0/17, + 124.71.128.0/18, + 124.71.192.0/19, + 124.71.224.0/20, + 124.71.240.0/21, + 124.71.250.0/23, + 124.71.254.0/23, + 124.72.0.0/13, + 124.88.0.0/13, + 124.108.8.0/21, + 124.108.40.0/21, + 124.109.96.0/21, + 124.112.0.0/13, + 124.126.0.0/15, + 124.128.0.0/13, + 124.147.128.0/17, + 124.150.137.0/24, + 124.151.0.0/16, + 124.152.0.0/16, + 124.160.0.0/13, + 124.172.87.0/24, + 124.172.88.0/23, + 124.172.91.0/24, + 124.172.93.0/24, + 124.172.128.0/17, + 124.173.64.0/18, + 124.173.128.0/18, + 124.173.205.0/24, + 124.174.0.0/15, + 124.192.0.0/15, + 124.196.0.0/16, + 124.197.56.0/21, + 124.200.0.0/13, + 124.220.0.0/14, + 124.224.0.0/12, + 124.240.0.0/17, + 124.240.128.0/18, + 124.242.0.0/16, + 124.243.192.0/18, + 124.248.0.0/17, + 124.249.0.0/16, + 124.250.0.0/15, + 124.254.0.0/18, + 125.31.192.0/18, + 125.32.0.0/12, + 125.58.128.0/17, + 125.61.128.0/17, + 125.62.0.0/18, + 125.64.0.0/12, + 125.80.0.0/13, + 125.88.0.0/14, + 125.92.0.0/16, + 125.93.0.0/20, + 125.93.16.0/21, + 125.93.28.0/22, + 125.93.32.0/19, + 125.93.64.0/18, + 125.93.128.0/17, + 125.94.0.0/15, + 125.96.0.0/15, + 125.98.0.0/16, + 125.104.0.0/13, + 125.112.0.0/12, + 125.169.0.0/16, + 125.171.0.0/16, + 125.208.0.0/18, + 125.210.0.0/15, + 125.213.0.0/17, + 125.214.96.0/19, + 125.215.0.0/18, + 125.216.0.0/13, + 125.254.128.0/17, + 128.108.0.0/16, + 129.28.0.0/16, + 129.204.0.0/16, + 129.211.0.0/16, + 132.232.0.0/16, + 132.237.134.0/24, + 134.49.240.0/22, + 134.175.0.0/16, + 137.59.59.0/24, + 137.59.88.0/22, + 138.252.72.0/23, + 138.252.78.0/23, + 138.252.96.0/24, + 138.252.118.0/23, + 138.252.162.0/23, + 139.5.56.0/21, + 139.5.80.0/22, + 139.5.92.0/22, + 139.5.128.0/22, + 139.5.160.0/22, + 139.5.192.0/22, + 139.5.204.0/22, + 139.5.212.0/22, + 139.5.244.0/22, + 139.9.0.0/16, + 139.95.0.0/20, + 139.95.16.0/22, + 139.95.22.0/23, + 139.95.24.0/21, + 139.95.32.0/21, + 139.95.40.0/23, + 139.95.64.0/23, + 139.95.96.0/22, + 139.95.128.0/17, + 139.129.0.0/16, + 139.148.0.0/16, + 139.155.0.0/16, + 139.159.0.0/19, + 139.159.32.0/21, + 139.159.40.0/22, + 139.159.52.0/22, + 139.159.56.0/21, + 139.159.64.0/19, + 139.159.96.0/20, + 139.159.112.0/22, + 139.159.116.0/23, + 139.159.120.0/21, + 139.159.128.0/17, + 139.170.0.0/16, + 139.176.0.0/16, + 139.183.0.0/18, + 139.183.64.0/19, + 139.183.96.0/22, + 139.183.101.0/24, + 139.183.102.0/23, + 139.183.104.0/21, + 139.183.112.0/20, + 139.183.128.0/18, + 139.183.192.0/20, + 139.183.208.0/23, + 139.183.211.0/24, + 139.183.212.0/22, + 139.183.216.0/21, + 139.183.224.0/19, + 139.186.0.0/16, + 139.189.0.0/18, + 139.189.64.0/19, + 139.189.96.0/20, + 139.189.112.0/21, + 139.189.120.0/22, + 139.189.124.0/23, + 139.189.126.0/24, + 139.189.128.0/17, + 139.196.0.0/15, + 139.198.0.0/18, + 139.198.66.0/23, + 139.198.68.0/22, + 139.198.72.0/21, + 139.198.80.0/20, + 139.198.96.0/19, + 139.198.128.0/17, + 139.199.0.0/16, + 139.200.0.0/13, + 139.208.0.0/13, + 139.217.0.0/16, + 139.219.0.0/16, + 139.220.0.0/15, + 139.224.0.0/16, + 139.226.0.0/15, + 140.75.0.0/16, + 140.143.0.0/16, + 140.179.0.0/16, + 140.205.0.0/16, + 140.206.0.0/15, + 140.210.0.0/16, + 140.224.0.0/16, + 140.237.0.0/16, + 140.240.0.0/16, + 140.243.0.0/16, + 140.246.0.0/16, + 140.249.0.0/16, + 140.250.0.0/16, + 140.255.0.0/16, + 142.86.200.0/21, + 142.86.208.0/20, + 142.86.224.0/20, + 142.86.240.0/21, + 142.86.248.0/22, + 142.86.254.0/23, + 143.64.0.0/16, + 144.0.0.0/16, + 144.7.0.0/16, + 144.12.0.0/16, + 144.36.147.0/24, + 144.48.64.0/22, + 144.48.88.0/22, + 144.48.156.0/22, + 144.48.180.0/22, + 144.48.184.0/22, + 144.48.204.0/22, + 144.48.208.0/21, + 144.52.0.0/16, + 144.79.4.0/23, + 144.79.10.0/23, + 144.79.28.0/23, + 144.79.42.0/23, + 144.79.54.0/23, + 144.79.84.0/23, + 144.79.148.0/23, + 144.123.0.0/16, + 144.255.0.0/16, + 145.14.71.0/24, + 145.14.72.0/22, + 145.14.81.0/24, + 145.14.82.0/24, + 145.14.84.0/24, + 145.14.86.0/24, + 145.14.88.0/24, + 145.14.90.0/24, + 146.56.192.0/18, + 146.196.56.0/22, + 146.196.68.0/22, + 146.196.72.0/22, + 146.196.92.0/22, + 146.196.112.0/21, + 146.196.124.0/22, + 146.222.79.0/24, + 146.222.81.0/24, + 146.222.94.0/24, + 147.78.132.0/24, + 147.78.134.0/24, + 147.139.0.0/17, + 147.139.128.0/18, + 147.139.192.0/19, + 147.139.224.0/20, + 147.139.240.0/21, + 147.139.248.0/22, + 148.70.0.0/16, + 149.41.64.0/18, + 149.41.192.0/19, + 149.129.0.0/20, + 149.129.16.0/21, + 149.129.32.0/19, + 149.129.64.0/18, + 149.129.128.0/19, + 149.129.168.0/21, + 149.129.176.0/20, + 149.129.192.0/18, + 150.0.0.0/16, + 150.115.0.0/16, + 150.121.0.0/16, + 150.122.0.0/16, + 150.129.136.0/22, + 150.129.192.0/22, + 150.129.252.0/22, + 150.138.0.0/15, + 150.158.0.0/16, + 150.223.0.0/16, + 150.242.0.0/21, + 150.242.8.0/22, + 150.242.28.0/22, + 150.242.44.0/22, + 150.242.48.0/21, + 150.242.56.0/22, + 150.242.76.0/22, + 150.242.80.0/22, + 150.242.92.0/22, + 150.242.96.0/22, + 150.242.112.0/21, + 150.242.120.0/22, + 150.242.152.0/22, + 150.242.156.0/23, + 150.242.160.0/21, + 150.242.168.0/22, + 150.242.184.0/21, + 150.242.192.0/22, + 150.242.212.0/24, + 150.242.215.0/24, + 150.242.224.0/22, + 150.242.232.0/21, + 150.242.240.0/21, + 150.242.248.0/22, + 150.248.0.0/16, + 150.255.0.0/16, + 151.158.14.0/23, + 151.158.78.0/23, + 151.158.80.0/23, + 151.158.122.0/23, + 152.104.128.0/17, + 152.136.0.0/16, + 153.0.0.0/16, + 153.3.0.0/16, + 153.34.0.0/15, + 153.36.0.0/15, + 153.99.0.0/16, + 153.101.0.0/16, + 153.118.0.0/15, + 154.8.128.0/17, + 154.72.44.0/24, + 154.72.47.0/24, + 155.126.176.0/23, + 156.107.160.0/23, + 156.107.170.0/24, + 156.107.179.0/24, + 156.107.181.0/24, + 156.227.20.0/24, + 156.236.12.0/24, + 156.236.17.0/24, + 156.245.1.0/24, + 157.0.0.0/16, + 157.10.34.0/23, + 157.10.36.0/23, + 157.10.112.0/23, + 157.10.118.0/23, + 157.10.130.0/23, + 157.10.218.0/23, + 157.10.220.0/23, + 157.10.246.0/23, + 157.15.74.0/23, + 157.15.94.0/23, + 157.15.100.0/22, + 157.15.104.0/23, + 157.18.0.0/16, + 157.20.136.0/23, + 157.20.194.0/23, + 157.20.246.0/23, + 157.61.0.0/16, + 157.66.43.0/24, + 157.66.70.0/23, + 157.66.88.0/21, + 157.66.164.0/23, + 157.66.244.0/23, + 157.119.8.0/21, + 157.119.16.0/22, + 157.119.28.0/22, + 157.119.132.0/22, + 157.119.136.0/21, + 157.119.144.0/20, + 157.119.160.0/21, + 157.119.172.0/22, + 157.119.192.0/21, + 157.119.240.0/22, + 157.119.252.0/22, + 157.122.0.0/16, + 157.148.0.0/16, + 157.156.0.0/16, + 157.255.0.0/16, + 158.60.0.0/16, + 158.79.0.0/16, + 158.140.252.0/23, + 158.140.255.0/24, + 159.27.0.0/16, + 159.75.0.0/16, + 159.226.0.0/16, + 160.19.76.0/23, + 160.19.82.0/23, + 160.19.208.0/21, + 160.19.216.0/22, + 160.20.16.0/22, + 160.20.48.0/22, + 160.20.130.0/23, + 160.22.58.0/23, + 160.22.82.0/23, + 160.22.148.0/23, + 160.22.188.0/23, + 160.22.224.0/23, + 160.22.230.0/23, + 160.22.244.0/22, + 160.25.10.0/23, + 160.25.12.0/23, + 160.25.194.0/23, + 160.30.40.0/23, + 160.30.194.0/23, + 160.30.196.0/22, + 160.30.230.0/23, + 160.83.110.0/24, + 160.187.223.0/24, + 160.187.252.0/22, + 160.191.0.0/23, + 160.191.104.0/23, + 160.191.110.0/23, + 160.202.60.0/23, + 160.202.62.0/24, + 160.202.148.0/22, + 160.202.152.0/22, + 160.202.210.0/23, + 160.202.212.0/22, + 160.202.216.0/21, + 160.202.224.0/19, + 160.236.78.0/23, + 160.236.148.0/23, + 160.236.162.0/23, + 160.236.235.0/24, + 160.236.250.0/23, + 160.250.14.0/23, + 160.250.16.0/22, + 160.250.84.0/23, + 160.250.90.0/23, + 160.250.102.0/23, + 160.250.104.0/23, + 160.250.160.0/23, + 160.250.170.0/23, + 160.250.214.0/23, + 160.250.252.0/23, + 161.117.0.0/16, + 161.163.0.0/21, + 161.163.28.0/23, + 161.189.0.0/16, + 161.207.0.0/16, + 161.248.20.0/23, + 161.248.42.0/23, + 161.248.92.0/23, + 161.248.110.0/23, + 161.248.112.0/24, + 161.248.136.0/24, + 162.4.54.0/23, + 162.4.168.0/23, + 162.4.198.0/23, + 162.4.202.0/23, + 162.4.230.0/23, + 162.14.0.0/19, + 162.14.32.0/21, + 162.14.43.0/24, + 162.14.44.0/22, + 162.14.52.0/22, + 162.14.56.0/22, + 162.14.64.0/18, + 162.14.128.0/17, + 162.105.0.0/16, + 163.0.0.0/16, + 163.47.4.0/22, + 163.52.28.0/23, + 163.52.76.0/23, + 163.52.108.0/23, + 163.52.246.0/23, + 163.53.0.0/20, + 163.53.36.0/22, + 163.53.40.0/21, + 163.53.48.0/20, + 163.53.64.0/22, + 163.53.88.0/21, + 163.53.96.0/19, + 163.53.128.0/21, + 163.53.136.0/22, + 163.53.160.0/20, + 163.53.188.0/22, + 163.53.240.0/22, + 163.61.63.0/24, + 163.61.178.0/23, + 163.61.202.0/23, + 163.61.214.0/23, + 163.125.0.0/16, + 163.128.30.0/23, + 163.128.152.0/23, + 163.128.236.0/23, + 163.128.254.0/23, + 163.142.0.0/16, + 163.177.0.0/16, + 163.179.0.0/16, + 163.204.0.0/16, + 163.223.28.0/23, + 163.223.32.0/23, + 163.223.72.0/23, + 163.223.124.0/23, + 163.223.222.0/23, + 163.227.34.0/23, + 163.227.40.0/23, + 163.227.74.0/23, + 163.227.76.0/22, + 163.227.80.0/23, + 163.228.0.0/16, + 163.244.246.0/24, + 164.52.5.0/24, + 164.52.61.0/24, + 164.52.66.0/24, + 164.52.75.0/24, + 164.52.78.0/24, + 164.52.85.0/24, + 164.52.99.0/24, + 165.101.4.0/23, + 165.101.66.0/23, + 165.101.70.0/24, + 165.101.144.0/23, + 165.101.170.0/23, + 165.101.208.0/23, + 165.101.244.0/23, + 166.111.0.0/16, + 167.139.0.0/16, + 167.189.0.0/16, + 167.220.244.0/22, + 168.160.0.0/16, + 170.33.15.0/24, + 170.33.20.0/22, + 170.33.24.0/24, + 170.33.29.0/24, + 170.33.30.0/23, + 170.33.32.0/22, + 170.33.41.0/24, + 170.33.64.0/23, + 170.33.66.0/24, + 170.33.68.0/23, + 170.33.72.0/23, + 170.33.76.0/22, + 170.33.80.0/22, + 170.33.84.0/24, + 170.33.104.0/22, + 170.33.112.0/23, + 170.33.114.0/24, + 170.33.129.0/24, + 170.33.130.0/23, + 170.33.136.0/23, + 170.33.138.0/24, + 170.33.168.0/23, + 170.33.192.0/23, + 170.179.0.0/16, + 171.8.0.0/13, + 171.34.0.0/15, + 171.36.0.0/14, + 171.40.0.0/13, + 171.80.0.0/12, + 171.104.0.0/13, + 171.112.0.0/12, + 171.208.0.0/12, + 172.81.192.0/18, + 175.0.0.0/12, + 175.16.0.0/13, + 175.24.0.0/14, + 175.30.0.0/15, + 175.42.0.0/15, + 175.44.0.0/16, + 175.46.0.0/15, + 175.48.0.0/12, + 175.64.0.0/11, + 175.102.0.0/16, + 175.106.128.0/17, + 175.111.144.0/20, + 175.111.160.0/20, + 175.111.184.0/22, + 175.146.0.0/15, + 175.148.0.0/14, + 175.152.0.0/14, + 175.158.96.0/22, + 175.160.0.0/12, + 175.176.156.0/22, + 175.176.176.0/22, + 175.176.188.0/22, + 175.178.0.0/16, + 175.184.128.0/18, + 175.185.0.0/16, + 175.186.0.0/15, + 175.188.0.0/14, + 177.201.160.0/19, + 177.203.96.0/19, + 177.203.224.0/19, + 178.236.234.0/24, + 179.236.0.0/19, + 180.76.16.0/20, + 180.76.32.0/19, + 180.76.64.0/18, + 180.76.128.0/17, + 180.77.0.0/16, + 180.78.0.0/15, + 180.84.0.0/15, + 180.86.0.0/16, + 180.88.0.0/14, + 180.94.56.0/21, + 180.94.96.0/20, + 180.94.120.0/21, + 180.95.128.0/17, + 180.96.0.0/11, + 180.129.128.0/17, + 180.130.0.0/16, + 180.136.0.0/13, + 180.148.16.0/21, + 180.148.152.0/21, + 180.148.216.0/21, + 180.148.224.0/19, + 180.149.128.0/19, + 180.150.160.0/19, + 180.152.0.0/13, + 180.160.0.0/12, + 180.178.112.0/21, + 180.178.192.0/18, + 180.184.0.0/14, + 180.188.0.0/17, + 180.189.148.0/22, + 180.200.252.0/22, + 180.201.0.0/16, + 180.202.0.0/15, + 180.208.0.0/15, + 180.210.212.0/22, + 180.212.0.0/15, + 180.222.224.0/19, + 180.233.0.0/18, + 180.233.64.0/19, + 180.233.144.0/22, + 180.235.64.0/19, + 180.235.112.0/22, + 182.16.144.0/21, + 182.16.192.0/19, + 182.18.0.0/17, + 182.23.184.0/21, + 182.23.200.0/21, + 182.32.0.0/12, + 182.48.96.0/19, + 182.49.0.0/16, + 182.50.0.0/22, + 182.50.6.0/24, + 182.50.8.0/21, + 182.50.112.0/20, + 182.51.0.0/16, + 182.54.0.0/17, + 182.54.244.0/22, + 182.61.0.0/17, + 182.61.128.0/19, + 182.61.192.0/18, + 182.80.0.0/13, + 182.88.0.0/14, + 182.92.0.0/16, + 182.96.0.0/11, + 182.128.0.0/12, + 182.144.0.0/13, + 182.157.0.0/16, + 182.160.64.0/19, + 182.174.0.0/15, + 182.200.0.0/13, + 182.236.128.0/17, + 182.237.24.0/21, + 182.238.0.0/16, + 182.239.0.0/19, + 182.240.0.0/13, + 182.254.0.0/16, + 182.255.60.0/22, + 183.0.0.0/10, + 183.64.0.0/13, + 183.78.160.0/21, + 183.78.180.0/22, + 183.81.172.0/24, + 183.81.180.0/22, + 183.84.0.0/15, + 183.91.37.0/24, + 183.91.39.0/24, + 183.91.40.0/21, + 183.91.48.0/23, + 183.91.50.0/24, + 183.91.52.0/22, + 183.91.56.0/24, + 183.91.61.0/24, + 183.91.128.0/22, + 183.91.136.0/21, + 183.91.144.0/20, + 183.92.0.0/14, + 183.128.0.0/11, + 183.160.0.0/13, + 183.168.0.0/15, + 183.170.0.0/16, + 183.172.0.0/14, + 183.182.0.0/21, + 183.182.8.0/23, + 183.182.11.0/24, + 183.182.12.0/22, + 183.182.16.0/23, + 183.182.23.0/24, + 183.182.24.0/21, + 183.184.0.0/13, + 183.192.0.0/10, + 185.2.48.0/24, + 185.75.173.0/24, + 185.75.174.0/24, + 185.192.56.0/22, + 188.131.128.0/17, + 192.51.172.0/23, + 192.55.46.0/23, + 192.55.68.0/22, + 192.102.204.0/22, + 192.124.154.0/24, + 192.140.128.0/21, + 192.140.136.0/22, + 192.140.156.0/22, + 192.140.160.0/19, + 192.140.192.0/20, + 192.140.208.0/21, + 192.140.244.0/23, + 192.144.128.0/17, + 192.163.11.0/24, + 192.232.34.0/23, + 192.232.44.0/22, + 192.232.97.0/24, + 193.112.0.0/16, + 193.119.0.0/19, + 194.138.202.0/23, + 194.138.245.0/24, + 198.11.128.0/20, + 198.11.145.0/24, + 198.11.146.0/23, + 198.11.148.0/22, + 198.11.152.0/21, + 198.11.160.0/19, + 198.175.100.0/22, + 198.208.17.0/24, + 198.208.19.0/24, + 198.208.30.0/24, + 198.208.61.0/24, + 198.208.63.0/24, + 198.208.67.0/24, + 198.208.112.0/23, + 199.182.239.0/24, + 199.244.144.0/24, + 202.0.100.0/23, + 202.0.122.0/23, + 202.0.176.0/23, + 202.0.178.0/24, + 202.3.128.0/23, + 202.4.128.0/19, + 202.4.252.0/22, + 202.5.208.0/21, + 202.5.216.0/22, + 202.6.6.0/23, + 202.6.66.0/23, + 202.6.72.0/23, + 202.6.87.0/24, + 202.6.88.0/23, + 202.6.92.0/23, + 202.6.103.0/24, + 202.6.108.0/24, + 202.6.110.0/23, + 202.6.114.0/24, + 202.6.176.0/20, + 202.6.202.0/23, + 202.8.0.0/24, + 202.8.2.0/23, + 202.8.4.0/23, + 202.8.12.0/24, + 202.8.24.0/24, + 202.8.77.0/24, + 202.8.128.0/19, + 202.8.192.0/20, + 202.9.32.0/24, + 202.9.34.0/23, + 202.9.48.0/23, + 202.9.51.0/24, + 202.9.52.0/23, + 202.9.54.0/24, + 202.9.57.0/24, + 202.9.58.0/23, + 202.10.64.0/20, + 202.10.112.0/20, + 202.12.1.0/24, + 202.12.2.0/24, + 202.12.17.0/24, + 202.12.18.0/24, + 202.12.72.0/24, + 202.12.84.0/23, + 202.12.96.0/24, + 202.12.98.0/23, + 202.12.106.0/24, + 202.12.111.0/24, + 202.12.116.0/24, + 202.14.64.0/23, + 202.14.69.0/24, + 202.14.73.0/24, + 202.14.74.0/23, + 202.14.76.0/24, + 202.14.78.0/23, + 202.14.88.0/24, + 202.14.97.0/24, + 202.14.104.0/23, + 202.14.108.0/23, + 202.14.111.0/24, + 202.14.114.0/23, + 202.14.118.0/23, + 202.14.124.0/23, + 202.14.127.0/24, + 202.14.129.0/24, + 202.14.135.0/24, + 202.14.136.0/24, + 202.14.149.0/24, + 202.14.151.0/24, + 202.14.157.0/24, + 202.14.158.0/23, + 202.14.169.0/24, + 202.14.170.0/23, + 202.14.172.0/22, + 202.14.176.0/24, + 202.14.184.0/23, + 202.14.208.0/23, + 202.14.213.0/24, + 202.14.219.0/24, + 202.14.220.0/24, + 202.14.222.0/23, + 202.14.225.0/24, + 202.14.226.0/23, + 202.14.231.0/24, + 202.14.235.0/24, + 202.14.236.0/22, + 202.14.246.0/24, + 202.14.251.0/24, + 202.20.66.0/24, + 202.20.79.0/24, + 202.20.87.0/24, + 202.20.88.0/23, + 202.20.90.0/24, + 202.20.94.0/23, + 202.20.114.0/24, + 202.20.117.0/24, + 202.20.120.0/24, + 202.20.125.0/24, + 202.20.126.0/23, + 202.21.48.0/20, + 202.21.131.0/24, + 202.21.132.0/24, + 202.21.141.0/24, + 202.21.142.0/24, + 202.21.147.0/24, + 202.21.148.0/24, + 202.21.150.0/23, + 202.21.152.0/23, + 202.21.154.0/24, + 202.21.156.0/24, + 202.22.248.0/21, + 202.27.12.0/24, + 202.27.14.0/24, + 202.27.136.0/23, + 202.36.226.0/24, + 202.38.0.0/22, + 202.38.8.0/21, + 202.38.48.0/20, + 202.38.64.0/18, + 202.38.128.0/21, + 202.38.136.0/23, + 202.38.140.0/22, + 202.38.146.0/23, + 202.38.149.0/24, + 202.38.150.0/23, + 202.38.152.0/22, + 202.38.156.0/24, + 202.38.158.0/23, + 202.38.164.0/22, + 202.38.168.0/23, + 202.38.170.0/24, + 202.38.176.0/23, + 202.38.184.0/21, + 202.38.192.0/18, + 202.40.4.0/23, + 202.40.7.0/24, + 202.40.15.0/24, + 202.40.135.0/24, + 202.40.136.0/24, + 202.40.140.0/24, + 202.40.143.0/24, + 202.40.144.0/23, + 202.40.150.0/24, + 202.40.155.0/24, + 202.40.156.0/24, + 202.40.158.0/23, + 202.40.162.0/24, + 202.41.8.0/23, + 202.41.11.0/24, + 202.41.12.0/23, + 202.41.128.0/24, + 202.41.130.0/23, + 202.41.152.0/21, + 202.41.192.0/24, + 202.41.196.0/22, + 202.41.200.0/22, + 202.41.240.0/20, + 202.43.76.0/22, + 202.43.144.0/20, + 202.44.16.0/20, + 202.44.48.0/22, + 202.44.67.0/24, + 202.44.74.0/24, + 202.44.97.0/24, + 202.44.129.0/24, + 202.44.132.0/23, + 202.44.146.0/23, + 202.45.0.0/23, + 202.45.2.0/24, + 202.45.15.0/24, + 202.45.16.0/20, + 202.46.16.0/23, + 202.46.18.0/24, + 202.46.20.0/23, + 202.46.35.0/24, + 202.46.36.0/23, + 202.46.38.0/24, + 202.46.40.0/24, + 202.46.43.0/24, + 202.46.44.0/24, + 202.46.46.0/23, + 202.46.128.0/24, + 202.46.224.0/20, + 202.47.82.0/23, + 202.47.96.0/20, + 202.47.126.0/24, + 202.47.128.0/24, + 202.47.130.0/23, + 202.52.33.0/24, + 202.52.34.0/24, + 202.52.47.0/24, + 202.52.143.0/24, + 202.53.140.0/24, + 202.53.143.0/24, + 202.55.0.0/23, + 202.55.4.0/24, + 202.55.18.0/23, + 202.55.20.0/22, + 202.55.24.0/22, + 202.55.28.0/23, + 202.55.31.0/24, + 202.57.192.0/20, + 202.57.212.0/22, + 202.57.216.0/22, + 202.57.240.0/20, + 202.58.0.0/24, + 202.58.112.0/22, + 202.58.122.0/23, + 202.59.0.0/23, + 202.59.212.0/22, + 202.59.236.0/24, + 202.59.240.0/24, + 202.60.48.0/21, + 202.60.96.0/21, + 202.60.112.0/20, + 202.60.132.0/22, + 202.60.136.0/21, + 202.60.144.0/20, + 202.61.68.0/22, + 202.61.76.0/22, + 202.61.88.0/22, + 202.61.123.0/24, + 202.61.127.0/24, + 202.62.112.0/22, + 202.62.248.0/22, + 202.62.252.0/24, + 202.62.255.0/24, + 202.63.80.0/20, + 202.63.160.0/19, + 202.63.248.0/22, + 202.63.253.0/24, + 202.65.0.0/21, + 202.65.8.0/23, + 202.65.96.0/20, + 202.66.169.0/24, + 202.66.170.0/23, + 202.67.0.0/22, + 202.69.4.0/23, + 202.69.16.0/20, + 202.70.0.0/19, + 202.70.96.0/20, + 202.70.192.0/20, + 202.71.32.0/20, + 202.72.40.0/21, + 202.72.80.0/20, + 202.72.112.0/20, + 202.73.128.0/22, + 202.73.240.0/20, + 202.74.8.0/21, + 202.74.36.0/24, + 202.74.42.0/24, + 202.74.52.0/24, + 202.74.80.0/20, + 202.74.254.0/23, + 202.75.208.0/20, + 202.75.252.0/22, + 202.76.252.0/22, + 202.77.80.0/21, + 202.77.92.0/22, + 202.78.8.0/21, + 202.79.224.0/21, + 202.79.248.0/22, + 202.80.192.0/20, + 202.81.0.0/22, + 202.81.176.0/20, + 202.83.252.0/22, + 202.84.0.0/20, + 202.84.16.0/23, + 202.84.22.0/24, + 202.84.24.0/21, + 202.85.208.0/20, + 202.86.249.0/24, + 202.86.252.0/22, + 202.87.80.0/20, + 202.88.32.0/22, + 202.89.8.0/21, + 202.89.96.0/22, + 202.89.108.0/22, + 202.89.119.0/24, + 202.89.232.0/21, + 202.90.16.0/20, + 202.90.37.0/24, + 202.90.96.0/19, + 202.90.193.0/24, + 202.90.196.0/24, + 202.90.205.0/24, + 202.90.224.0/20, + 202.91.0.0/22, + 202.91.96.0/20, + 202.91.176.0/20, + 202.91.224.0/19, + 202.92.8.0/21, + 202.92.48.0/20, + 202.92.252.0/22, + 202.93.252.0/22, + 202.94.74.0/24, + 202.94.81.0/24, + 202.94.92.0/22, + 202.95.0.0/24, + 202.95.2.0/23, + 202.95.24.0/21, + 202.95.240.0/21, + 202.96.0.0/12, + 202.112.0.0/13, + 202.120.0.0/15, + 202.122.0.0/21, + 202.122.32.0/21, + 202.122.64.0/19, + 202.122.112.0/20, + 202.122.132.0/24, + 202.123.96.0/20, + 202.123.116.0/22, + 202.123.120.0/22, + 202.124.16.0/21, + 202.124.24.0/22, + 202.125.107.0/24, + 202.125.109.0/24, + 202.125.112.0/20, + 202.125.176.0/20, + 202.127.0.0/21, + 202.127.12.0/22, + 202.127.16.0/20, + 202.127.40.0/21, + 202.127.48.0/20, + 202.127.112.0/20, + 202.127.128.0/19, + 202.127.192.0/20, + 202.127.212.0/22, + 202.127.216.0/21, + 202.127.224.0/19, + 202.129.208.0/24, + 202.130.0.0/19, + 202.130.39.0/24, + 202.130.224.0/19, + 202.131.16.0/21, + 202.131.48.0/20, + 202.131.208.0/20, + 202.133.32.0/20, + 202.134.58.0/24, + 202.134.128.0/20, + 202.134.208.0/20, + 202.136.48.0/20, + 202.136.72.0/23, + 202.136.80.0/23, + 202.136.208.0/20, + 202.136.224.0/20, + 202.136.248.0/22, + 202.136.255.0/24, + 202.137.231.0/24, + 202.140.140.0/22, + 202.140.144.0/20, + 202.141.160.0/19, + 202.142.16.0/20, + 202.143.4.0/22, + 202.143.16.0/20, + 202.143.32.0/20, + 202.143.56.0/21, + 202.143.100.0/22, + 202.143.104.0/22, + 202.144.196.0/22, + 202.146.160.0/20, + 202.146.184.0/23, + 202.146.186.0/24, + 202.146.188.0/22, + 202.146.196.0/22, + 202.146.200.0/21, + 202.147.144.0/20, + 202.148.32.0/20, + 202.148.64.0/18, + 202.149.32.0/19, + 202.149.160.0/19, + 202.149.224.0/20, + 202.149.240.0/21, + 202.149.248.0/22, + 202.149.252.0/23, + 202.149.255.0/24, + 202.150.16.0/20, + 202.150.32.0/20, + 202.150.56.0/22, + 202.150.192.0/20, + 202.150.224.0/19, + 202.151.0.0/22, + 202.151.33.0/24, + 202.151.128.0/19, + 202.152.176.0/20, + 202.153.0.0/22, + 202.153.7.0/24, + 202.153.48.0/20, + 202.157.192.0/19, + 202.158.160.0/19, + 202.158.242.0/24, + 202.160.140.0/23, + 202.160.143.0/24, + 202.160.156.0/22, + 202.162.67.0/24, + 202.162.75.0/24, + 202.164.0.0/20, + 202.164.96.0/19, + 202.165.176.0/20, + 202.165.239.0/24, + 202.165.240.0/23, + 202.165.243.0/24, + 202.165.245.0/24, + 202.165.251.0/24, + 202.165.252.0/22, + 202.166.224.0/19, + 202.168.80.0/22, + 202.168.128.0/20, + 202.168.160.0/19, + 202.170.128.0/19, + 202.170.216.0/21, + 202.170.224.0/19, + 202.171.216.0/21, + 202.171.232.0/24, + 202.171.235.0/24, + 202.172.0.0/22, + 202.172.7.0/24, + 202.173.0.0/22, + 202.173.6.0/24, + 202.173.8.0/21, + 202.173.112.0/22, + 202.173.224.0/19, + 202.174.64.0/20, + 202.174.124.0/22, + 202.176.224.0/19, + 202.179.160.0/20, + 202.179.240.0/21, + 202.180.128.0/19, + 202.180.208.0/21, + 202.181.8.0/22, + 202.181.28.0/22, + 202.181.112.0/20, + 202.182.32.0/20, + 202.182.192.0/19, + 202.189.0.0/18, + 202.189.80.0/20, + 202.189.184.0/21, + 202.191.0.0/24, + 202.191.68.0/22, + 202.191.72.0/21, + 202.191.80.0/20, + 202.192.0.0/12, + 203.0.4.0/22, + 203.0.10.0/23, + 203.0.18.0/24, + 203.0.24.0/24, + 203.0.42.0/23, + 203.0.45.0/24, + 203.0.46.0/23, + 203.0.81.0/24, + 203.0.82.0/23, + 203.0.90.0/23, + 203.0.96.0/23, + 203.0.104.0/21, + 203.0.114.0/23, + 203.0.122.0/24, + 203.0.128.0/24, + 203.0.130.0/23, + 203.0.132.0/22, + 203.0.137.0/24, + 203.0.142.0/24, + 203.0.144.0/24, + 203.0.146.0/24, + 203.0.148.0/24, + 203.0.150.0/23, + 203.0.152.0/24, + 203.0.177.0/24, + 203.0.224.0/24, + 203.1.4.0/22, + 203.1.18.0/24, + 203.1.26.0/23, + 203.1.65.0/24, + 203.1.66.0/23, + 203.1.70.0/23, + 203.1.76.0/23, + 203.1.90.0/24, + 203.1.97.0/24, + 203.1.98.0/23, + 203.1.100.0/22, + 203.1.108.0/24, + 203.1.253.0/24, + 203.1.254.0/24, + 203.2.64.0/21, + 203.2.73.0/24, + 203.2.112.0/21, + 203.2.126.0/23, + 203.2.140.0/24, + 203.2.150.0/24, + 203.2.152.0/22, + 203.2.156.0/23, + 203.2.160.0/21, + 203.2.180.0/23, + 203.2.196.0/23, + 203.2.209.0/24, + 203.2.214.0/23, + 203.2.226.0/23, + 203.2.229.0/24, + 203.2.236.0/23, + 203.3.68.0/24, + 203.3.72.0/23, + 203.3.75.0/24, + 203.3.80.0/21, + 203.3.96.0/22, + 203.3.105.0/24, + 203.3.112.0/21, + 203.3.120.0/24, + 203.3.123.0/24, + 203.3.135.0/24, + 203.3.139.0/24, + 203.3.143.0/24, + 203.4.132.0/23, + 203.4.134.0/24, + 203.4.151.0/24, + 203.4.152.0/22, + 203.4.174.0/23, + 203.4.180.0/24, + 203.4.186.0/24, + 203.4.205.0/24, + 203.4.208.0/22, + 203.4.227.0/24, + 203.4.230.0/23, + 203.5.4.0/23, + 203.5.7.0/24, + 203.5.8.0/23, + 203.5.11.0/24, + 203.5.21.0/24, + 203.5.22.0/24, + 203.5.44.0/24, + 203.5.46.0/23, + 203.5.52.0/22, + 203.5.56.0/23, + 203.5.60.0/23, + 203.5.114.0/23, + 203.5.118.0/24, + 203.5.120.0/24, + 203.5.172.0/24, + 203.5.180.0/23, + 203.5.182.0/24, + 203.5.185.0/24, + 203.5.186.0/24, + 203.5.188.0/23, + 203.5.190.0/24, + 203.5.195.0/24, + 203.5.214.0/23, + 203.5.218.0/23, + 203.6.131.0/24, + 203.6.136.0/24, + 203.6.138.0/23, + 203.6.142.0/24, + 203.6.150.0/23, + 203.6.157.0/24, + 203.6.159.0/24, + 203.6.224.0/20, + 203.6.248.0/23, + 203.7.129.0/24, + 203.7.138.0/23, + 203.7.147.0/24, + 203.7.150.0/23, + 203.7.158.0/24, + 203.7.192.0/23, + 203.7.200.0/24, + 203.8.0.0/24, + 203.8.8.0/24, + 203.8.23.0/24, + 203.8.25.0/24, + 203.8.70.0/24, + 203.8.82.0/24, + 203.8.86.0/23, + 203.8.91.0/24, + 203.8.110.0/23, + 203.8.115.0/24, + 203.8.166.0/23, + 203.8.169.0/24, + 203.8.173.0/24, + 203.8.184.0/24, + 203.8.186.0/23, + 203.8.190.0/23, + 203.8.192.0/24, + 203.8.197.0/24, + 203.8.198.0/23, + 203.8.203.0/24, + 203.8.209.0/24, + 203.8.210.0/23, + 203.8.212.0/22, + 203.8.217.0/24, + 203.8.220.0/24, + 203.9.32.0/24, + 203.9.36.0/23, + 203.9.57.0/24, + 203.9.63.0/24, + 203.9.65.0/24, + 203.9.70.0/23, + 203.9.72.0/24, + 203.9.75.0/24, + 203.9.76.0/23, + 203.9.96.0/22, + 203.9.100.0/23, + 203.9.108.0/24, + 203.9.158.0/24, + 203.10.34.0/24, + 203.10.56.0/24, + 203.10.74.0/23, + 203.10.84.0/22, + 203.10.88.0/24, + 203.10.95.0/24, + 203.10.125.0/24, + 203.11.70.0/24, + 203.11.76.0/22, + 203.11.82.0/24, + 203.11.84.0/22, + 203.11.100.0/22, + 203.11.109.0/24, + 203.11.117.0/24, + 203.11.122.0/24, + 203.11.126.0/24, + 203.11.136.0/22, + 203.11.141.0/24, + 203.11.142.0/23, + 203.11.180.0/22, + 203.11.208.0/22, + 203.12.16.0/24, + 203.12.19.0/24, + 203.12.24.0/24, + 203.12.57.0/24, + 203.12.65.0/24, + 203.12.66.0/24, + 203.12.70.0/23, + 203.12.87.0/24, + 203.12.91.0/24, + 203.12.93.0/24, + 203.12.95.0/24, + 203.12.100.0/23, + 203.12.103.0/24, + 203.12.114.0/24, + 203.12.118.0/24, + 203.12.130.0/24, + 203.12.137.0/24, + 203.12.196.0/22, + 203.12.211.0/24, + 203.12.219.0/24, + 203.12.226.0/24, + 203.12.240.0/22, + 203.13.18.0/24, + 203.13.24.0/24, + 203.13.44.0/23, + 203.13.81.0/24, + 203.13.88.0/23, + 203.13.92.0/22, + 203.13.173.0/24, + 203.13.224.0/23, + 203.13.227.0/24, + 203.13.233.0/24, + 203.14.24.0/22, + 203.14.33.0/24, + 203.14.56.0/24, + 203.14.61.0/24, + 203.14.62.0/24, + 203.14.104.0/24, + 203.14.114.0/23, + 203.14.118.0/24, + 203.14.162.0/24, + 203.14.192.0/24, + 203.14.194.0/23, + 203.14.214.0/24, + 203.14.231.0/24, + 203.14.246.0/24, + 203.15.0.0/20, + 203.15.20.0/23, + 203.15.22.0/24, + 203.15.87.0/24, + 203.15.88.0/23, + 203.15.105.0/24, + 203.15.112.0/24, + 203.15.114.0/23, + 203.15.119.0/24, + 203.15.130.0/23, + 203.15.149.0/24, + 203.15.151.0/24, + 203.15.156.0/22, + 203.15.174.0/24, + 203.15.227.0/24, + 203.15.232.0/21, + 203.15.240.0/23, + 203.15.246.0/24, + 203.16.10.0/24, + 203.16.12.0/23, + 203.16.16.0/21, + 203.16.27.0/24, + 203.16.38.0/24, + 203.16.49.0/24, + 203.16.50.0/23, + 203.16.58.0/24, + 203.16.63.0/24, + 203.16.133.0/24, + 203.16.161.0/24, + 203.16.162.0/24, + 203.16.186.0/23, + 203.16.228.0/24, + 203.16.238.0/24, + 203.16.240.0/24, + 203.16.245.0/24, + 203.17.2.0/24, + 203.17.18.0/24, + 203.17.28.0/24, + 203.17.39.0/24, + 203.17.56.0/24, + 203.17.74.0/23, + 203.17.88.0/23, + 203.17.136.0/24, + 203.17.164.0/24, + 203.17.187.0/24, + 203.17.190.0/23, + 203.17.231.0/24, + 203.17.233.0/24, + 203.17.248.0/23, + 203.17.255.0/24, + 203.18.2.0/23, + 203.18.4.0/24, + 203.18.7.0/24, + 203.18.31.0/24, + 203.18.37.0/24, + 203.18.48.0/23, + 203.18.52.0/24, + 203.18.72.0/22, + 203.18.80.0/23, + 203.18.87.0/24, + 203.18.100.0/23, + 203.18.105.0/24, + 203.18.107.0/24, + 203.18.110.0/24, + 203.18.129.0/24, + 203.18.131.0/24, + 203.18.132.0/23, + 203.18.144.0/24, + 203.18.153.0/24, + 203.18.199.0/24, + 203.18.208.0/24, + 203.18.211.0/24, + 203.18.215.0/24, + 203.19.1.0/24, + 203.19.18.0/24, + 203.19.24.0/24, + 203.19.30.0/24, + 203.19.36.0/24, + 203.19.39.0/24, + 203.19.41.0/24, + 203.19.44.0/23, + 203.19.46.0/24, + 203.19.58.0/24, + 203.19.60.0/23, + 203.19.64.0/24, + 203.19.68.0/24, + 203.19.72.0/24, + 203.19.101.0/24, + 203.19.111.0/24, + 203.19.131.0/24, + 203.19.133.0/24, + 203.19.144.0/24, + 203.19.147.0/24, + 203.19.149.0/24, + 203.19.156.0/24, + 203.19.176.0/24, + 203.19.178.0/23, + 203.19.208.0/24, + 203.19.228.0/22, + 203.19.233.0/24, + 203.19.242.0/24, + 203.19.248.0/23, + 203.19.255.0/24, + 203.20.17.0/24, + 203.20.40.0/23, + 203.20.44.0/24, + 203.20.48.0/24, + 203.20.61.0/24, + 203.20.65.0/24, + 203.20.84.0/23, + 203.20.89.0/24, + 203.20.106.0/23, + 203.20.115.0/24, + 203.20.117.0/24, + 203.20.118.0/23, + 203.20.122.0/24, + 203.20.126.0/23, + 203.20.135.0/24, + 203.20.138.0/23, + 203.20.150.0/24, + 203.20.230.0/24, + 203.20.232.0/24, + 203.20.236.0/24, + 203.21.0.0/23, + 203.21.2.0/24, + 203.21.8.0/24, + 203.21.10.0/24, + 203.21.18.0/24, + 203.21.33.0/24, + 203.21.34.0/24, + 203.21.41.0/24, + 203.21.44.0/24, + 203.21.68.0/24, + 203.21.82.0/24, + 203.21.96.0/22, + 203.21.124.0/24, + 203.21.136.0/23, + 203.21.145.0/24, + 203.21.206.0/24, + 203.22.24.0/24, + 203.22.28.0/23, + 203.22.31.0/24, + 203.22.68.0/24, + 203.22.76.0/24, + 203.22.78.0/24, + 203.22.84.0/24, + 203.22.87.0/24, + 203.22.92.0/22, + 203.22.99.0/24, + 203.22.106.0/24, + 203.22.122.0/23, + 203.22.131.0/24, + 203.22.163.0/24, + 203.22.166.0/24, + 203.22.170.0/24, + 203.22.176.0/21, + 203.22.194.0/24, + 203.22.242.0/23, + 203.22.245.0/24, + 203.22.246.0/24, + 203.22.252.0/23, + 203.23.0.0/24, + 203.23.47.0/24, + 203.23.61.0/24, + 203.23.62.0/23, + 203.23.73.0/24, + 203.23.85.0/24, + 203.23.92.0/22, + 203.23.98.0/24, + 203.23.107.0/24, + 203.23.112.0/24, + 203.23.130.0/24, + 203.23.140.0/23, + 203.23.172.0/24, + 203.23.182.0/24, + 203.23.186.0/23, + 203.23.192.0/24, + 203.23.197.0/24, + 203.23.198.0/24, + 203.23.204.0/22, + 203.23.224.0/24, + 203.23.226.0/23, + 203.23.228.0/22, + 203.23.249.0/24, + 203.23.251.0/24, + 203.24.13.0/24, + 203.24.18.0/24, + 203.24.27.0/24, + 203.24.43.0/24, + 203.24.56.0/24, + 203.24.58.0/24, + 203.24.67.0/24, + 203.24.74.0/24, + 203.24.79.0/24, + 203.24.80.0/23, + 203.24.84.0/23, + 203.24.86.0/24, + 203.24.90.0/24, + 203.24.111.0/24, + 203.24.112.0/24, + 203.24.116.0/24, + 203.24.122.0/23, + 203.24.145.0/24, + 203.24.152.0/23, + 203.24.157.0/24, + 203.24.161.0/24, + 203.24.167.0/24, + 203.24.186.0/23, + 203.24.199.0/24, + 203.24.202.0/24, + 203.24.212.0/23, + 203.24.217.0/24, + 203.24.219.0/24, + 203.24.244.0/24, + 203.25.19.0/24, + 203.25.20.0/23, + 203.25.46.0/24, + 203.25.48.0/21, + 203.25.64.0/23, + 203.25.91.0/24, + 203.25.99.0/24, + 203.25.100.0/24, + 203.25.106.0/24, + 203.25.131.0/24, + 203.25.135.0/24, + 203.25.138.0/24, + 203.25.147.0/24, + 203.25.153.0/24, + 203.25.154.0/23, + 203.25.164.0/24, + 203.25.166.0/24, + 203.25.174.0/23, + 203.25.180.0/24, + 203.25.182.0/24, + 203.25.191.0/24, + 203.25.199.0/24, + 203.25.200.0/24, + 203.25.202.0/23, + 203.25.208.0/20, + 203.25.229.0/24, + 203.25.235.0/24, + 203.25.236.0/24, + 203.25.242.0/24, + 203.26.12.0/24, + 203.26.34.0/24, + 203.26.49.0/24, + 203.26.50.0/24, + 203.26.55.0/24, + 203.26.56.0/23, + 203.26.60.0/24, + 203.26.65.0/24, + 203.26.68.0/24, + 203.26.76.0/24, + 203.26.80.0/24, + 203.26.84.0/24, + 203.26.97.0/24, + 203.26.102.0/23, + 203.26.115.0/24, + 203.26.116.0/24, + 203.26.129.0/24, + 203.26.143.0/24, + 203.26.144.0/24, + 203.26.148.0/23, + 203.26.154.0/24, + 203.26.158.0/23, + 203.26.170.0/24, + 203.26.173.0/24, + 203.26.176.0/24, + 203.26.185.0/24, + 203.26.202.0/23, + 203.26.210.0/24, + 203.26.214.0/24, + 203.26.222.0/24, + 203.26.224.0/24, + 203.26.228.0/24, + 203.26.232.0/24, + 203.27.0.0/24, + 203.27.10.0/24, + 203.27.15.0/24, + 203.27.16.0/24, + 203.27.20.0/24, + 203.27.22.0/23, + 203.27.40.0/24, + 203.27.45.0/24, + 203.27.53.0/24, + 203.27.65.0/24, + 203.27.66.0/24, + 203.27.81.0/24, + 203.27.88.0/24, + 203.27.102.0/24, + 203.27.109.0/24, + 203.27.117.0/24, + 203.27.121.0/24, + 203.27.122.0/23, + 203.27.125.0/24, + 203.27.200.0/24, + 203.27.202.0/24, + 203.27.233.0/24, + 203.27.241.0/24, + 203.27.250.0/24, + 203.28.10.0/24, + 203.28.12.0/24, + 203.28.33.0/24, + 203.28.34.0/23, + 203.28.43.0/24, + 203.28.44.0/24, + 203.28.54.0/24, + 203.28.56.0/24, + 203.28.73.0/24, + 203.28.74.0/24, + 203.28.76.0/24, + 203.28.86.0/24, + 203.28.88.0/24, + 203.28.112.0/24, + 203.28.131.0/24, + 203.28.136.0/24, + 203.28.140.0/24, + 203.28.145.0/24, + 203.28.165.0/24, + 203.28.169.0/24, + 203.28.170.0/24, + 203.28.178.0/23, + 203.28.185.0/24, + 203.28.187.0/24, + 203.28.196.0/24, + 203.28.226.0/23, + 203.28.239.0/24, + 203.29.2.0/24, + 203.29.8.0/23, + 203.29.13.0/24, + 203.29.14.0/24, + 203.29.28.0/24, + 203.29.46.0/24, + 203.29.57.0/24, + 203.29.61.0/24, + 203.29.63.0/24, + 203.29.69.0/24, + 203.29.73.0/24, + 203.29.81.0/24, + 203.29.90.0/24, + 203.29.95.0/24, + 203.29.100.0/24, + 203.29.103.0/24, + 203.29.112.0/24, + 203.29.120.0/22, + 203.29.182.0/23, + 203.29.187.0/24, + 203.29.189.0/24, + 203.29.190.0/24, + 203.29.205.0/24, + 203.29.210.0/24, + 203.29.217.0/24, + 203.29.227.0/24, + 203.29.231.0/24, + 203.29.233.0/24, + 203.29.234.0/24, + 203.29.248.0/24, + 203.29.254.0/23, + 203.30.16.0/23, + 203.30.25.0/24, + 203.30.27.0/24, + 203.30.29.0/24, + 203.30.66.0/24, + 203.30.81.0/24, + 203.30.87.0/24, + 203.30.111.0/24, + 203.30.121.0/24, + 203.30.123.0/24, + 203.30.152.0/24, + 203.30.156.0/24, + 203.30.162.0/24, + 203.30.173.0/24, + 203.30.175.0/24, + 203.30.187.0/24, + 203.30.194.0/24, + 203.30.217.0/24, + 203.30.220.0/24, + 203.30.222.0/24, + 203.30.232.0/23, + 203.30.235.0/24, + 203.30.240.0/23, + 203.30.246.0/24, + 203.30.250.0/23, + 203.31.45.0/24, + 203.31.46.0/24, + 203.31.49.0/24, + 203.31.51.0/24, + 203.31.54.0/23, + 203.31.69.0/24, + 203.31.72.0/24, + 203.31.80.0/24, + 203.31.85.0/24, + 203.31.97.0/24, + 203.31.105.0/24, + 203.31.106.0/24, + 203.31.108.0/23, + 203.31.124.0/24, + 203.31.162.0/24, + 203.31.174.0/24, + 203.31.177.0/24, + 203.31.181.0/24, + 203.31.187.0/24, + 203.31.189.0/24, + 203.31.204.0/24, + 203.31.220.0/24, + 203.31.222.0/23, + 203.31.225.0/24, + 203.31.229.0/24, + 203.31.248.0/23, + 203.31.253.0/24, + 203.32.20.0/24, + 203.32.48.0/23, + 203.32.56.0/24, + 203.32.60.0/24, + 203.32.62.0/24, + 203.32.68.0/23, + 203.32.76.0/24, + 203.32.81.0/24, + 203.32.84.0/23, + 203.32.95.0/24, + 203.32.102.0/24, + 203.32.105.0/24, + 203.32.130.0/24, + 203.32.133.0/24, + 203.32.140.0/24, + 203.32.152.0/24, + 203.32.186.0/23, + 203.32.192.0/24, + 203.32.196.0/24, + 203.32.203.0/24, + 203.32.204.0/23, + 203.32.212.0/24, + 203.33.4.0/24, + 203.33.7.0/24, + 203.33.11.0/24, + 203.33.12.0/22, + 203.33.21.0/24, + 203.33.26.0/24, + 203.33.32.0/24, + 203.33.63.0/24, + 203.33.64.0/24, + 203.33.67.0/24, + 203.33.68.0/24, + 203.33.73.0/24, + 203.33.79.0/24, + 203.33.100.0/24, + 203.33.122.0/24, + 203.33.129.0/24, + 203.33.131.0/24, + 203.33.145.0/24, + 203.33.156.0/24, + 203.33.158.0/23, + 203.33.174.0/24, + 203.33.185.0/24, + 203.33.200.0/24, + 203.33.202.0/23, + 203.33.204.0/24, + 203.33.206.0/23, + 203.33.214.0/23, + 203.33.224.0/23, + 203.33.226.0/24, + 203.33.233.0/24, + 203.33.243.0/24, + 203.33.250.0/24, + 203.34.4.0/24, + 203.34.21.0/24, + 203.34.27.0/24, + 203.34.39.0/24, + 203.34.48.0/23, + 203.34.54.0/24, + 203.34.56.0/23, + 203.34.67.0/24, + 203.34.69.0/24, + 203.34.76.0/24, + 203.34.92.0/24, + 203.34.106.0/24, + 203.34.113.0/24, + 203.34.147.0/24, + 203.34.150.0/24, + 203.34.152.0/23, + 203.34.161.0/24, + 203.34.162.0/24, + 203.34.187.0/24, + 203.34.192.0/23, + 203.34.195.0/24, + 203.34.198.0/24, + 203.34.204.0/22, + 203.34.232.0/24, + 203.34.240.0/24, + 203.34.242.0/24, + 203.34.245.0/24, + 203.34.251.0/24, + 203.55.2.0/23, + 203.55.4.0/24, + 203.55.10.0/24, + 203.55.13.0/24, + 203.55.22.0/24, + 203.55.30.0/24, + 203.55.93.0/24, + 203.55.101.0/24, + 203.55.109.0/24, + 203.55.110.0/24, + 203.55.116.0/23, + 203.55.119.0/24, + 203.55.128.0/23, + 203.55.146.0/23, + 203.55.192.0/24, + 203.55.196.0/24, + 203.55.218.0/23, + 203.55.221.0/24, + 203.55.224.0/24, + 203.56.1.0/24, + 203.56.4.0/24, + 203.56.12.0/24, + 203.56.24.0/24, + 203.56.38.0/24, + 203.56.40.0/24, + 203.56.46.0/24, + 203.56.48.0/23, + 203.56.51.0/24, + 203.56.52.0/23, + 203.56.54.0/24, + 203.56.68.0/23, + 203.56.82.0/23, + 203.56.84.0/23, + 203.56.95.0/24, + 203.56.110.0/24, + 203.56.121.0/24, + 203.56.161.0/24, + 203.56.169.0/24, + 203.56.172.0/23, + 203.56.175.0/24, + 203.56.183.0/24, + 203.56.185.0/24, + 203.56.187.0/24, + 203.56.192.0/24, + 203.56.198.0/24, + 203.56.201.0/24, + 203.56.208.0/23, + 203.56.210.0/24, + 203.56.214.0/24, + 203.56.216.0/24, + 203.56.227.0/24, + 203.56.228.0/24, + 203.56.231.0/24, + 203.56.232.0/24, + 203.56.240.0/24, + 203.56.252.0/24, + 203.56.254.0/24, + 203.57.5.0/24, + 203.57.6.0/24, + 203.57.12.0/23, + 203.57.28.0/24, + 203.57.39.0/24, + 203.57.46.0/24, + 203.57.58.0/24, + 203.57.61.0/24, + 203.57.66.0/24, + 203.57.69.0/24, + 203.57.70.0/23, + 203.57.73.0/24, + 203.57.90.0/24, + 203.57.101.0/24, + 203.57.109.0/24, + 203.57.123.0/24, + 203.57.157.0/24, + 203.57.200.0/24, + 203.57.202.0/24, + 203.57.206.0/24, + 203.57.222.0/24, + 203.57.224.0/20, + 203.57.246.0/23, + 203.57.249.0/24, + 203.57.253.0/24, + 203.57.254.0/23, + 203.62.2.0/24, + 203.62.131.0/24, + 203.62.139.0/24, + 203.62.161.0/24, + 203.62.197.0/24, + 203.62.228.0/22, + 203.62.234.0/24, + 203.62.246.0/24, + 203.65.240.0/22, + 203.76.160.0/22, + 203.76.168.0/22, + 203.76.208.0/21, + 203.76.216.0/22, + 203.76.240.0/22, + 203.77.180.0/22, + 203.78.48.0/20, + 203.78.156.0/22, + 203.79.0.0/20, + 203.80.4.0/23, + 203.80.32.0/20, + 203.80.57.0/24, + 203.80.129.0/24, + 203.80.132.0/22, + 203.80.140.0/24, + 203.80.142.0/23, + 203.80.144.0/20, + 203.81.1.0/24, + 203.81.2.0/24, + 203.81.5.0/24, + 203.81.7.0/24, + 203.81.16.0/20, + 203.81.244.0/22, + 203.82.0.0/23, + 203.82.16.0/24, + 203.82.20.0/23, + 203.82.22.0/24, + 203.82.112.0/20, + 203.82.224.0/20, + 203.83.0.0/22, + 203.83.12.0/22, + 203.83.56.0/21, + 203.83.224.0/20, + 203.86.0.0/18, + 203.86.64.0/19, + 203.86.96.0/22, + 203.86.100.0/23, + 203.86.102.0/24, + 203.86.104.0/23, + 203.86.107.0/24, + 203.86.108.0/24, + 203.86.110.0/24, + 203.86.112.0/24, + 203.86.114.0/24, + 203.86.116.0/23, + 203.86.120.0/24, + 203.86.250.0/24, + 203.86.254.0/23, + 203.88.32.0/19, + 203.88.192.0/19, + 203.89.8.0/23, + 203.89.13.0/24, + 203.89.14.0/23, + 203.89.133.0/24, + 203.89.136.0/22, + 203.89.144.0/24, + 203.90.8.0/21, + 203.90.128.0/18, + 203.90.192.0/19, + 203.91.32.0/19, + 203.91.96.0/20, + 203.91.120.0/21, + 203.92.0.0/22, + 203.92.6.0/24, + 203.92.160.0/19, + 203.93.0.0/16, + 203.94.0.0/19, + 203.95.0.0/21, + 203.95.96.0/19, + 203.95.128.0/18, + 203.95.200.0/21, + 203.95.208.0/22, + 203.95.224.0/19, + 203.99.8.0/23, + 203.99.12.0/24, + 203.99.14.0/23, + 203.99.16.0/23, + 203.99.19.0/24, + 203.99.20.0/22, + 203.99.24.0/21, + 203.99.80.0/20, + 203.100.32.0/20, + 203.100.48.0/22, + 203.100.53.0/24, + 203.100.55.0/24, + 203.100.58.0/24, + 203.100.60.0/24, + 203.100.63.0/24, + 203.100.80.0/20, + 203.100.96.0/19, + 203.100.192.0/20, + 203.104.32.0/20, + 203.105.96.0/19, + 203.105.128.0/19, + 203.107.0.0/17, + 203.110.160.0/19, + 203.110.208.0/20, + 203.110.232.0/23, + 203.110.234.0/24, + 203.114.80.0/20, + 203.114.244.0/22, + 203.118.192.0/19, + 203.118.241.0/24, + 203.118.248.0/22, + 203.119.24.0/21, + 203.119.32.0/22, + 203.119.80.0/24, + 203.119.83.0/24, + 203.119.85.0/24, + 203.119.113.0/24, + 203.119.114.0/23, + 203.119.116.0/22, + 203.119.121.0/24, + 203.119.122.0/23, + 203.119.126.0/23, + 203.119.128.0/17, + 203.123.58.0/24, + 203.128.32.0/19, + 203.128.96.0/19, + 203.128.224.0/22, + 203.128.228.0/24, + 203.128.231.0/24, + 203.129.8.0/24, + 203.129.10.0/23, + 203.129.12.0/22, + 203.130.32.0/19, + 203.132.32.0/19, + 203.134.240.0/21, + 203.135.96.0/19, + 203.135.160.0/20, + 203.142.219.0/24, + 203.142.224.0/19, + 203.144.96.0/22, + 203.144.100.0/24, + 203.144.102.0/24, + 203.144.106.0/23, + 203.144.108.0/22, + 203.144.112.0/20, + 203.145.0.0/19, + 203.148.0.0/18, + 203.148.64.0/20, + 203.148.80.0/22, + 203.148.86.0/23, + 203.149.92.0/22, + 203.152.64.0/19, + 203.152.128.0/19, + 203.153.0.0/22, + 203.156.192.0/18, + 203.158.16.0/21, + 203.160.104.0/21, + 203.160.129.0/24, + 203.160.192.0/19, + 203.161.0.0/22, + 203.161.183.0/24, + 203.161.192.0/19, + 203.166.160.0/19, + 203.167.28.0/22, + 203.168.0.0/19, + 203.170.58.0/23, + 203.171.0.0/22, + 203.171.208.0/24, + 203.171.224.0/20, + 203.174.4.0/24, + 203.174.6.0/24, + 203.174.96.0/19, + 203.175.128.0/19, + 203.175.192.0/18, + 203.176.0.0/18, + 203.176.64.0/19, + 203.176.168.0/21, + 203.184.80.0/20, + 203.185.189.0/24, + 203.187.160.0/19, + 203.189.0.0/23, + 203.189.6.0/23, + 203.189.112.0/22, + 203.189.192.0/19, + 203.189.240.0/22, + 203.190.96.0/20, + 203.190.249.0/24, + 203.191.0.0/23, + 203.191.2.0/24, + 203.191.5.0/24, + 203.191.7.0/24, + 203.191.16.0/20, + 203.191.64.0/18, + 203.191.133.0/24, + 203.191.144.0/20, + 203.192.0.0/19, + 203.193.224.0/19, + 203.194.120.0/21, + 203.195.64.0/19, + 203.195.112.0/23, + 203.195.114.0/24, + 203.195.116.0/22, + 203.195.128.0/17, + 203.196.0.0/21, + 203.196.28.0/22, + 203.201.181.0/24, + 203.201.182.0/24, + 203.202.236.0/22, + 203.205.64.0/19, + 203.205.130.0/23, + 203.205.132.0/23, + 203.205.148.0/22, + 203.205.152.0/23, + 203.205.154.0/24, + 203.205.156.0/24, + 203.205.158.0/23, + 203.205.160.0/20, + 203.205.176.0/21, + 203.205.184.0/22, + 203.205.189.0/24, + 203.205.190.0/24, + 203.205.200.0/21, + 203.205.208.0/21, + 203.205.216.0/23, + 203.205.225.0/24, + 203.205.226.0/23, + 203.205.228.0/22, + 203.205.240.0/21, + 203.207.64.0/18, + 203.207.128.0/17, + 203.208.32.0/19, + 203.209.224.0/19, + 203.212.0.0/20, + 203.212.80.0/20, + 203.215.232.0/21, + 203.217.164.0/22, + 203.223.16.0/24, + 203.223.21.0/24, + 203.223.22.0/23, + 205.204.96.0/19, + 206.109.70.0/23, + 206.109.75.0/24, + 210.2.0.0/19, + 210.5.0.0/19, + 210.5.56.0/24, + 210.5.58.0/23, + 210.5.60.0/22, + 210.5.128.0/19, + 210.7.56.0/21, + 210.12.0.0/15, + 210.14.64.0/19, + 210.14.112.0/20, + 210.14.128.0/17, + 210.15.0.0/17, + 210.15.128.0/18, + 210.16.128.0/18, + 210.21.0.0/16, + 210.22.0.0/16, + 210.23.32.0/19, + 210.25.0.0/16, + 210.26.0.0/15, + 210.28.0.0/14, + 210.32.0.0/12, + 210.51.0.0/16, + 210.52.0.0/15, + 210.56.192.0/19, + 210.72.0.0/14, + 210.76.0.0/15, + 210.78.0.0/16, + 210.79.64.0/18, + 210.79.224.0/19, + 210.82.0.0/15, + 210.87.72.0/23, + 210.87.114.0/23, + 210.87.128.0/18, + 210.185.192.0/18, + 210.192.96.0/19, + 211.64.0.0/13, + 211.80.0.0/12, + 211.96.0.0/15, + 211.98.0.0/16, + 211.99.0.0/19, + 211.99.64.0/18, + 211.99.128.0/17, + 211.100.0.0/15, + 211.102.0.0/17, + 211.102.128.0/21, + 211.102.144.0/21, + 211.102.160.0/20, + 211.102.192.0/18, + 211.103.0.0/16, + 211.136.0.0/13, + 211.144.0.0/14, + 211.148.0.0/16, + 211.149.0.0/19, + 211.149.33.0/24, + 211.149.34.0/23, + 211.149.48.0/21, + 211.149.64.0/21, + 211.149.80.0/21, + 211.149.96.0/19, + 211.149.128.0/17, + 211.150.0.0/15, + 211.152.0.0/17, + 211.152.128.0/20, + 211.152.144.0/22, + 211.152.150.0/23, + 211.152.152.0/21, + 211.152.160.0/19, + 211.152.192.0/18, + 211.153.0.0/16, + 211.154.0.0/16, + 211.155.0.0/18, + 211.155.66.0/23, + 211.155.68.0/22, + 211.155.72.0/21, + 211.155.80.0/20, + 211.155.96.0/19, + 211.155.128.0/18, + 211.155.208.0/20, + 211.155.224.0/19, + 211.156.0.0/14, + 211.160.0.0/14, + 211.164.0.0/15, + 211.166.0.0/16, + 211.167.0.0/19, + 211.167.64.0/18, + 211.167.128.0/17, + 212.64.0.0/17, + 212.129.128.0/17, + 218.0.0.0/12, + 218.16.0.0/13, + 218.24.0.0/14, + 218.28.0.0/15, + 218.30.0.0/19, + 218.30.32.0/22, + 218.30.36.0/23, + 218.30.38.0/24, + 218.30.40.0/22, + 218.30.45.0/24, + 218.30.46.0/23, + 218.30.48.0/21, + 218.30.56.0/22, + 218.30.60.0/23, + 218.30.64.0/18, + 218.30.128.0/17, + 218.31.0.0/16, + 218.56.0.0/13, + 218.64.0.0/11, + 218.96.0.0/16, + 218.97.0.0/17, + 218.97.160.0/19, + 218.97.192.0/18, + 218.98.0.0/15, + 218.100.88.0/21, + 218.100.96.0/19, + 218.100.128.0/17, + 218.104.0.0/14, + 218.108.0.0/15, + 218.185.192.0/19, + 218.185.240.0/24, + 218.185.242.0/24, + 218.185.244.0/24, + 218.185.246.0/23, + 218.192.0.0/12, + 218.240.0.0/14, + 218.244.0.0/15, + 218.246.0.0/16, + 218.247.0.0/18, + 218.247.96.0/19, + 218.247.128.0/17, + 218.249.0.0/16, + 219.72.0.0/16, + 219.82.0.0/16, + 219.83.128.0/17, + 219.90.68.0/22, + 219.90.72.0/21, + 219.128.0.0/11, + 219.216.0.0/13, + 219.224.0.0/13, + 219.232.0.0/15, + 219.234.10.0/23, + 219.234.12.0/22, + 219.234.32.0/19, + 219.234.64.0/18, + 219.234.128.0/17, + 219.235.0.0/16, + 219.236.0.0/14, + 219.242.0.0/15, + 219.244.0.0/14, + 220.101.192.0/18, + 220.112.0.0/14, + 220.152.128.0/17, + 220.154.0.0/15, + 220.158.240.0/22, + 220.160.0.0/13, + 220.168.0.0/14, + 220.172.0.0/15, + 220.174.0.0/20, + 220.174.16.0/21, + 220.174.24.0/23, + 220.174.28.0/22, + 220.174.32.0/19, + 220.174.64.0/20, + 220.174.80.0/21, + 220.174.88.0/22, + 220.174.92.0/23, + 220.174.96.0/19, + 220.174.128.0/17, + 220.175.0.0/16, + 220.176.0.0/12, + 220.192.0.0/12, + 220.231.0.0/18, + 220.231.128.0/17, + 220.232.64.0/18, + 220.234.0.0/16, + 220.242.0.0/22, + 220.242.4.0/23, + 220.242.6.0/24, + 220.242.8.0/21, + 220.242.16.0/20, + 220.242.32.0/19, + 220.242.64.0/18, + 220.242.128.0/17, + 220.243.0.0/16, + 220.247.136.0/23, + 220.247.142.0/23, + 220.248.0.0/14, + 220.252.0.0/16, + 221.0.0.0/13, + 221.8.0.0/14, + 221.12.0.0/17, + 221.12.128.0/18, + 221.13.0.0/16, + 221.14.0.0/15, + 221.122.0.0/15, + 221.128.132.0/22, + 221.128.136.0/21, + 221.128.144.0/20, + 221.128.160.0/19, + 221.128.192.0/23, + 221.128.194.0/24, + 221.128.196.0/22, + 221.128.200.0/21, + 221.128.208.0/20, + 221.128.224.0/21, + 221.128.232.0/22, + 221.128.238.0/23, + 221.129.0.0/16, + 221.130.0.0/15, + 221.133.224.0/19, + 221.136.0.0/15, + 221.172.0.0/14, + 221.176.0.0/13, + 221.192.0.0/14, + 221.196.0.0/15, + 221.198.0.0/16, + 221.199.0.0/17, + 221.199.128.0/18, + 221.199.192.0/20, + 221.199.224.0/19, + 221.200.0.0/13, + 221.208.0.0/12, + 221.224.0.0/12, + 222.16.0.0/12, + 222.32.0.0/11, + 222.64.0.0/11, + 222.125.0.0/16, + 222.126.128.0/17, + 222.128.0.0/12, + 222.160.0.0/14, + 222.168.0.0/13, + 222.176.0.0/12, + 222.192.0.0/11, + 222.240.0.0/13, + 222.248.0.0/15, + 223.0.0.0/12, + 223.20.0.0/15, + 223.27.184.0/22, + 223.29.208.0/22, + 223.29.252.0/24, + 223.29.255.0/24, + 223.64.0.0/11, + 223.96.0.0/12, + 223.112.0.0/14, + 223.116.0.0/15, + 223.121.128.0/17, + 223.122.5.0/24, + 223.122.6.0/23, + 223.122.13.0/24, + 223.122.14.0/23, + 223.122.18.0/23, + 223.122.20.0/22, + 223.122.26.0/23, + 223.122.28.0/23, + 223.123.196.0/22, + 223.123.204.0/22, + 223.123.208.0/20, + 223.123.224.0/19, + 223.124.0.0/14, + 223.128.0.0/15, + 223.144.0.0/12, + 223.160.0.0/14, + 223.166.0.0/15, + 223.192.0.0/15, + 223.198.0.0/15, + 223.201.0.0/16, + 223.202.0.0/15, + 223.208.0.0/13, + 223.220.0.0/15, + 223.223.176.0/20, + 223.223.192.0/20, + 223.240.0.0/13, + 223.248.0.0/14, + 223.252.128.0/17, + 223.254.0.0/17, + 223.254.160.0/19, + 223.254.192.0/18, + 223.255.0.0/17, + 223.255.236.0/22, + 223.255.252.0/23 + } +} diff --git a/clashoo/files/usr/share/clashoo/rpc/rpc_async.sh b/clashoo/files/usr/share/clashoo/rpc/rpc_async.sh new file mode 100644 index 00000000..ee00dfd4 --- /dev/null +++ b/clashoo/files/usr/share/clashoo/rpc/rpc_async.sh @@ -0,0 +1,44 @@ +#!/bin/sh + +ACTION="$1" +LOG_FILE="/tmp/clash_update.txt" + +log_line() { + printf '%s - %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$1" >> "$LOG_FILE" +} + +run_init_async() { + local action="$1" + mkdir -p /tmp/lock >/dev/null 2>&1 + (flock /tmp/lock/clashoo_rpc_async.lock /etc/init.d/clashoo "$action" >/dev/null 2>&1 >"$LOG_FILE" 2>&1 &-; nohup /usr/share/clashoo/update/geoip.sh >/dev/null 2>&1 &2 + exit 1 + ;; +esac diff --git a/clashoo/files/usr/share/clashoo/ruleset/cn-ip.srs b/clashoo/files/usr/share/clashoo/ruleset/cn-ip.srs new file mode 100644 index 00000000..ef9118fa Binary files /dev/null and b/clashoo/files/usr/share/clashoo/ruleset/cn-ip.srs differ diff --git a/clashoo/files/usr/share/clashoo/ruleset/cn.mrs b/clashoo/files/usr/share/clashoo/ruleset/cn.mrs new file mode 100644 index 00000000..610f2d2d Binary files /dev/null and b/clashoo/files/usr/share/clashoo/ruleset/cn.mrs differ diff --git a/clashoo/files/usr/share/clashoo/ruleset/geolocation-!cn.srs b/clashoo/files/usr/share/clashoo/ruleset/geolocation-!cn.srs new file mode 100644 index 00000000..6f26b898 Binary files /dev/null and b/clashoo/files/usr/share/clashoo/ruleset/geolocation-!cn.srs differ diff --git a/clashoo/files/usr/share/clashoo/ruleset/geosite-cn.srs b/clashoo/files/usr/share/clashoo/ruleset/geosite-cn.srs new file mode 100644 index 00000000..216609fe Binary files /dev/null and b/clashoo/files/usr/share/clashoo/ruleset/geosite-cn.srs differ diff --git a/clashoo/files/usr/share/clashoo/ruleset/private-ip.srs b/clashoo/files/usr/share/clashoo/ruleset/private-ip.srs new file mode 100644 index 00000000..f825d93c Binary files /dev/null and b/clashoo/files/usr/share/clashoo/ruleset/private-ip.srs differ diff --git a/clashoo/files/usr/share/clashoo/runtime/backup.sh b/clashoo/files/usr/share/clashoo/runtime/backup.sh new file mode 100755 index 00000000..85bcc17c --- /dev/null +++ b/clashoo/files/usr/share/clashoo/runtime/backup.sh @@ -0,0 +1,21 @@ +#!/bin/sh +. /lib/functions.sh + +CURL_GROUP_CACHE="/usr/share/clashbackup/clash_gorup.json" +CURL_NOW_CACHE="/usr/share/clashbackup/clash_now.json" +CURL_CACHE="/usr/share/clashbackup/clash_curl.json" +HISTORY_PATH="/usr/share/clashbackup/history" +SECRET=$(uci get clashoo.config.dash_pass 2>/dev/null) +LAN_IP=$(uci get network.lan.ipaddr 2>/dev/null |awk -F '/' '{print $1}' 2>/dev/null) +PORT=$(uci get clashoo.config.dash_port 2>/dev/null) + +curl -m 5 --retry 2 -w %{http_code}"\n" -H "Authorization: Bearer ${SECRET}" -H "Content-Type:application/json" -X GET http://"$LAN_IP":"$PORT"/proxies > "$CURL_CACHE" 2>/dev/null +if [ "$(sed -n '$p' "$CURL_CACHE" 2>/dev/null)" = "200" ]; then + if [ ! -d /usr/share/clashbackup ];then + mkdir -p /usr/share/clashbackup 2>/dev/null + fi + cat "$CURL_CACHE" |jsonfilter -e '@["proxies"][@.type="Selector"]["name"]' > "$CURL_GROUP_CACHE" 2>/dev/null + cat "$CURL_CACHE" |jsonfilter -e '@["proxies"][@.type="Selector"]["now"]' > "$CURL_NOW_CACHE" 2>/dev/null + awk 'NR==FNR{a[i]=$0;i++}NR>FNR{print a[j]"#*#"$0;j++}' "$CURL_GROUP_CACHE" "$CURL_NOW_CACHE" > "$HISTORY_PATH" 2>/dev/null +fi +rm -rf /usr/share/clashbackup/clash_*.json 2>/dev/null diff --git a/clashoo/files/usr/share/clashoo/runtime/clashoo.sh b/clashoo/files/usr/share/clashoo/runtime/clashoo.sh new file mode 100755 index 00000000..35d7ebfe --- /dev/null +++ b/clashoo/files/usr/share/clashoo/runtime/clashoo.sh @@ -0,0 +1,353 @@ +#!/bin/sh + +REAL_LOG="/usr/share/clashoo/clashoo_real.txt" +UPDATE_LOG="/tmp/clash_update.txt" +LIST_FILE="/usr/share/clashbackup/confit_list.conf" +SUB_DIR="/usr/share/clashoo/config/sub" +TMP_PREFIX="/tmp/clash_sub_$$" +DEFAULT_SUB_UA='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36' + +subtype="$(uci -q get clashoo.config.subcri 2>/dev/null)" +config_name_raw="$(uci -q get clashoo.config.config_name 2>/dev/null)" +lang="$(uci -q get luci.main.lang 2>/dev/null)" + +log_text() { + if [ "$lang" = "en" ]; then + echo "$1" >"$REAL_LOG" + else + echo "$2" >"$REAL_LOG" + fi +} + +log_update() { + printf ' %s - %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$1" >>"$UPDATE_LOG" +} + +sanitize_name() { + local name + name="$1" + name="$(printf '%s' "$name" | tr 'A-Z' 'a-z')" + name="$(printf '%s' "$name" | sed -e 's/\.yaml$//' -e 's/\.yml$//')" + name="$(printf '%s' "$name" | tr ' /' '--')" + name="$(printf '%s' "$name" | sed -e 's/[^a-z0-9._-]/-/g' -e 's/--\+/-/g' -e 's/^[._-]*//' -e 's/[._-]*$//')" + printf '%s' "$name" +} + +sanitize_custom_name() { + local name + name="$1" + name="$(printf '%s' "$name" | sed -e 's/\.yaml$//' -e 's/\.yml$//')" + name="$(printf '%s' "$name" | tr ' /' '--')" + name="$(printf '%s' "$name" | sed -e 's/[\\]//g' -e 's/\.\.+/-/g' -e 's/--\+/-/g' -e 's/^[._-]*//' -e 's/[._-]*$//')" + printf '%s' "$name" +} + +url_decode() { + printf '%s' "$1" | sed 's/%/\\x/g' | xargs -0 printf '%b' 2>/dev/null || printf '%s' "$1" +} + +url_to_name() { + local url host qname decoded + url="$1" + + qname="$(printf '%s' "$url" | sed -n 's/.*[?&]filename=\([^&#]*\).*/\1/p')" + [ -n "$qname" ] || qname="$(printf '%s' "$url" | sed -n 's/.*[?&]name=\([^&#]*\).*/\1/p')" + if [ -n "$qname" ]; then + # URL decode then strip non-filename chars, keep CJK and alphanumeric + decoded="$(url_decode "$qname")" + decoded="$(printf '%s' "$decoded" | tr -d '\r\n' | sed -e 's/[[:space:]]/-/g' -e 's/[/\\:*?"<>|]//g' -e 's/\.yaml$//' -e 's/\.yml$//')" + [ -n "$decoded" ] && printf '%s' "$decoded" && return + fi + qname="$(sanitize_name "$qname")" + if [ -n "$qname" ]; then + printf '%s' "$qname" + return + fi + + host="$(printf '%s' "$url" | sed -e 's#^[a-zA-Z0-9+.-]*://##' -e 's#/.*$##' -e 's/:.*$//' -e 's#\..*$##')" + host="$(sanitize_name "$host")" + [ -n "$host" ] || host="sub" + printf '%s' "$host" +} + +next_available_name() { + local base try idx + base="$1" + base="$(printf '%s' "$base" | sed -e 's/\.yaml$//' -e 's/\.yml$//')" + base="$(printf '%s' "$base" | tr ' /' '--')" + base="$(printf '%s' "$base" | sed -e 's/[\\]//g' -e 's/\.\.+/-/g' -e 's/--\+/-/g' -e 's/^[._-]*//' -e 's/[._-]*$//')" + [ -n "$base" ] || base="sub" + + if [ ! -f "$SUB_DIR/${base}.yaml" ]; then + printf '%s' "$base" + return + fi + + idx=2 + while :; do + try="${base}-${idx}" + if [ ! -f "$SUB_DIR/${try}.yaml" ]; then + printf '%s' "$try" + return + fi + idx=$((idx + 1)) + done +} + +get_subscription_urls() { + uci -q show clashoo.config 2>/dev/null | awk -F"'" ' + /^clashoo.config.subscribe_url=/ { + if (NF >= 3) { + for (i = 2; i <= NF; i += 2) { + if (length($i) > 0) print $i + } + } else { + sub(/^clashoo.config.subscribe_url=/, "", $0) + if (length($0) > 0) print $0 + } + } + ' +} + +ensure_system_dns() { + local test_host + test_host="github.com" + if nslookup "$test_host" 127.0.0.1 >/dev/null 2>&1 || nslookup "$test_host" >/dev/null 2>&1; then + return 0 + fi + + uci delete dhcp.@dnsmasq[0].server >/dev/null 2>&1 + uci set dhcp.@dnsmasq[0].noresolv='0' >/dev/null 2>&1 + uci del_list dhcp.@dnsmasq[0].server='127.0.0.1#' >/dev/null 2>&1 + uci del_list dhcp.@dnsmasq[0].server='127.0.0.1#5300' >/dev/null 2>&1 + uci add_list dhcp.@dnsmasq[0].server='119.29.29.29' >/dev/null 2>&1 + uci add_list dhcp.@dnsmasq[0].server='223.5.5.5' >/dev/null 2>&1 + uci commit dhcp >/dev/null 2>&1 + /etc/init.d/dnsmasq restart >/dev/null 2>&1 + sleep 2 +} + +extract_host() { + printf '%s' "$1" | sed -e 's#^[a-zA-Z0-9+.-]*://##' -e 's#/.*$##' -e 's#:.*$##' -e 's#.*@##' +} + +resolve_via() { + local host dns + host="$1" + dns="$2" + nslookup "$host" "$dns" 2>/dev/null | awk ' + /^Address/ { + ip = $NF + if (ip ~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/ && + ip !~ /^127\./ && + ip !~ /^0\./ && + ip !~ /^198\.18\./ && + ip != "8.8.8.8" && ip != "8.8.4.4" && + ip != "1.1.1.1" && ip != "1.0.0.1" && + ip != "223.5.5.5" && ip != "223.6.6.6" && + ip != "119.29.29.29" && ip != "114.114.114.114") { + print ip + exit + } + }' +} + +curl_subscription() { + local url tmp hdr err ua host ip http_code rc + url="$1" + tmp="$2" + hdr="$3" + err="$4" + ua="$5" + host="$6" + ip="$7" + + rm -f "$tmp" "$hdr" "$err" >/dev/null 2>&1 + + set -- + if [ -n "$host" ] && [ -n "$ip" ]; then + set -- --resolve "$host:443:$ip" --resolve "$host:80:$ip" + fi + http_code="$(curl -sSL --connect-timeout 15 --max-time 60 \ + --speed-time 30 --speed-limit 1 --retry 2 \ + -H "User-Agent: ${ua}" -D "$hdr" -o "$tmp" \ + "$@" \ + -w '%{http_code}' "$url" 2>"$err")" + rc=$? + printf '%s\n' "$http_code" + return "$rc" +} + +download_subscription() { + local url target tmp hdr err rc http_code info_line ua err_msg + local host ip dns + url="$1" + target="$2" + tmp="${TMP_PREFIX}.yaml" + hdr="${TMP_PREFIX}.hdr" + err="${TMP_PREFIX}.err" + + ua="$(uci -q get clashoo.config.sub_ua 2>/dev/null)" + [ -n "$ua" ] || ua="$DEFAULT_SUB_UA" + + if command -v curl >/dev/null 2>&1; then + http_code="$(curl_subscription "$url" "$tmp" "$hdr" "$err" "$ua" "" "")" + rc=$? + + if [ "$rc" -ne 0 ] || [ "$http_code" = "000" ]; then + host="$(extract_host "$url")" + case "$host" in + ''|[-.]*|*[!A-Za-z0-9.-]*) host='' ;; + esac + if [ -n "$host" ]; then + for dns in 223.5.5.5 119.29.29.29 1.1.1.1 8.8.8.8; do + ip="$(resolve_via "$host" "$dns")" + [ -n "$ip" ] || continue + log_update "DNS 回退:${host} -> ${ip} (@${dns})" + http_code="$(curl_subscription "$url" "$tmp" "$hdr" "$err" "$ua" "$host" "$ip")" + rc=$? + [ "$rc" -eq 0 ] && [ "$http_code" = "200" ] && break + done + fi + fi + else + wget -q --timeout=20 \ + --user-agent="$ua" "$url" -O "$tmp" 2>"$err" + rc=$? + http_code="" + fi + + if [ "$rc" -ne 0 ]; then + err_msg="$(grep -a 'curl:' "$err" 2>/dev/null | tail -1)" + [ -z "$err_msg" ] && err_msg="$(tail -1 "$err" 2>/dev/null)" + log_update "下载失败:$(basename "$target") rc=${rc} ${err_msg}" + rm -f "$tmp" "$hdr" "$err" >/dev/null 2>&1 + return 1 + fi + + if [ -n "$http_code" ] && [ "$http_code" != "200" ]; then + log_update "下载失败:$(basename "$target") HTTP ${http_code}" + rm -f "$tmp" "$hdr" "$err" >/dev/null 2>&1 + return 1 + fi + + if ! grep -Eq '^(proxies|proxy-providers):' "$tmp" 2>/dev/null; then + log_update "校验失败:$(basename "$target") 内容不含 proxies/proxy-providers" + rm -f "$tmp" "$hdr" "$err" >/dev/null 2>&1 + return 1 + fi + + info_line="$(grep -i 'subscription-userinfo:' "$hdr" 2>/dev/null | head -1 | \ + sed 's/^[Ss]ubscription-[Uu]serinfo:[[:space:]]*//' | tr -d '\r')" + [ -n "$info_line" ] && printf '%s\n' "$info_line" > "${target}.info" || \ + rm -f "${target}.info" >/dev/null 2>&1 + + rm -f "$hdr" "$err" >/dev/null 2>&1 + mv "$tmp" "$target" >/dev/null 2>&1 || { + rm -f "$tmp" >/dev/null 2>&1 + return 1 + } + printf '%s\n' "$ua" >"${target}.ua" + + return 0 +} + +upsert_meta() { + local filename url typ tmpf + filename="$1" + url="$2" + typ="$3" + tmpf="${TMP_PREFIX}.list" + + [ -f "$LIST_FILE" ] || touch "$LIST_FILE" + awk -F '#' -v n="$filename" '$1 != n { print $0 }' "$LIST_FILE" >"$tmpf" + printf '%s#%s#%s\n' "$filename" "$url" "$typ" >>"$tmpf" + mv "$tmpf" "$LIST_FILE" +} + +cleanup_tmp() { + rm -f "${TMP_PREFIX}.yaml" "${TMP_PREFIX}.urls" "${TMP_PREFIX}.list" \ + "${TMP_PREFIX}.hdr" "${TMP_PREFIX}.err" >/dev/null 2>&1 +} + +trap cleanup_tmp EXIT INT TERM + +[ "$subtype" = "clash" ] || [ "$subtype" = "meta" ] || subtype="clash" + +mkdir -p "$SUB_DIR" /usr/share/clashbackup >/dev/null 2>&1 +[ -f "$LIST_FILE" ] || touch "$LIST_FILE" + +URLS_FILE="${TMP_PREFIX}.urls" +get_subscription_urls | sed '/^[[:space:]]*$/d' | awk '!seen[$0]++' >"$URLS_FILE" + +url_count="$(wc -l <"$URLS_FILE" 2>/dev/null | tr -d ' ')" +if [ -z "$url_count" ] || [ "$url_count" -eq 0 ]; then + log_update "未找到订阅链接" + log_text "No subscription URL found" "未找到订阅链接" + sleep 2 + log_text "Clash for OpenWRT" "Clash for OpenWRT" + exit 1 +fi + +ensure_system_dns +log_update "开始下载订阅(共 ${url_count} 条)" +log_text "Downloading subscription..." "开始下载订阅..." + +base_name="$(sanitize_custom_name "$config_name_raw")" +timestamp="$(date +%Y%m%d)" + +success=0 +failed=0 +idx=0 +first_file="" + +while IFS= read -r url; do + [ -n "$url" ] || continue + idx=$((idx + 1)) + + if [ -n "$base_name" ]; then + if [ "$url_count" -gt 1 ]; then + name_candidate="$(sanitize_name "${base_name}-${idx}")" + file_base="$(next_available_name "$name_candidate")" + else + file_base="$base_name" + fi + else + name_candidate="$(url_to_name "$url")-${timestamp}" + if [ "$url_count" -gt 1 ]; then + name_candidate="${name_candidate}-${idx}" + fi + file_base="$(next_available_name "$name_candidate")" + fi + + target_file="$SUB_DIR/${file_base}.yaml" + if download_subscription "$url" "$target_file"; then + upsert_meta "${file_base}.yaml" "$url" "$subtype" + log_update "订阅下载成功:${file_base}.yaml" + [ -n "$first_file" ] || first_file="$target_file" + success=$((success + 1)) + else + log_update "订阅下载失败:${file_base}.yaml" + failed=$((failed + 1)) + fi +done <"$URLS_FILE" + +if [ "$success" -gt 0 ]; then + use_config="$(uci -q get clashoo.config.use_config 2>/dev/null)" + if [ -z "$use_config" ] || [ ! -f "$use_config" ]; then + uci set clashoo.config.use_config="$first_file" + uci set clashoo.config.config_type='1' + uci commit clashoo + fi + log_text "Subscription download completed: ${success} success, ${failed} failed" "订阅下载完成:成功 ${success} 个,失败 ${failed} 个" + log_update "订阅下载完成:成功 ${success} 个,失败 ${failed} 个" + ret=0 +else + log_text "All subscription downloads failed" "订阅下载失败" + log_update "订阅下载失败:全部链接失败" + ret=1 +fi + +sleep 2 +log_text "Clash for OpenWRT" "Clash for OpenWRT" +exit "$ret" diff --git a/clashoo/files/usr/share/clashoo/runtime/dns_helpers.sh b/clashoo/files/usr/share/clashoo/runtime/dns_helpers.sh new file mode 100755 index 00000000..b4fa6b42 --- /dev/null +++ b/clashoo/files/usr/share/clashoo/runtime/dns_helpers.sh @@ -0,0 +1,389 @@ +#!/bin/sh +# Shared DNS helpers for ClashOO runtime generators. Keep POSIX sh compatible. + +dns_trim() { + printf '%s' "$1" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' +} + +dns_has_scheme() { + printf '%s' "$1" | grep -Eq '^[A-Za-z][A-Za-z0-9+.-]*://' +} + +dns_norm_protocol() { + case "$(dns_trim "${1:-}")" in + ''|'none') printf '' ;; + udp|'udp://') printf 'udp://' ;; + tcp|'tcp://') printf 'tcp://' ;; + dot|tls|'tls://') printf 'tls://' ;; + doh|https|'https://') printf 'https://' ;; + doq|quic|'quic://') printf 'quic://' ;; + *) printf '%s' "$1" ;; + esac +} + +dns_normalize_server() { + local address protocol port prefix + address=$(dns_trim "${1:-}") + protocol=$(dns_trim "${2:-}") + port=$(dns_trim "${3:-}") + + [ -n "$address" ] || return 0 + + if dns_has_scheme "$address"; then + printf '%s' "$address" + return 0 + fi + + prefix=$(dns_norm_protocol "$protocol") + if [ -n "$port" ]; then + printf '%s%s:%s' "$prefix" "$address" "$port" + else + printf '%s%s' "$prefix" "$address" + fi +} + +dns_yaml_sq() { + printf "%s" "$1" | sed "s/'/''/g" +} + +dns_yaml_list_item() { + local value + value=$(dns_yaml_sq "$1") + printf " - '%s'\n" "$value" +} + +# Route DNS upstreams by rules and preserve the original setting. +dns_mihomo_apply_respect_rules() { + local cfg="$1" enabled="${2:-0}" tmp="${1}.$$" + [ -f "$cfg" ] || return 0 + + awk -v enabled="$enabled" ' + function indent_len(s) { match(s, /[^ ]/); return RSTART ? RSTART - 1 : length(s) } + function spaces(n, out,i) { + out = "" + for (i = 0; i < n; i++) + out = out " " + return out + } + function trim_value(s) { + sub(/^[[:space:]]*/, "", s) + sub(/[[:space:]]*$/, "", s) + return s + } + function flush_dns( i, sp) { + if (!in_dns) + return + sp = spaces(child_indent > 0 ? child_indent : dns_indent + 2) + print dns_lines[1] + if (enabled == "1") { + print sp "# >>> clashoo:dns_leak_protect_respect_rules" + print sp "# original-respect-rules: " (original_value != "" ? original_value : "absent") + print sp "respect-rules: true" + print sp "# <<< clashoo:dns_leak_protect_respect_rules" + } else if (original_value != "" && original_value != "absent") { + print sp "respect-rules: " original_value + } + for (i = 2; i <= dns_n; i++) + print dns_lines[i] + in_dns = 0 + dns_n = 0 + } + + /^dns:[[:space:]]*$/ { + flush_dns() + in_dns = 1 + dns_indent = indent_len($0) + child_indent = -1 + dns_n = 1 + dns_lines[dns_n] = $0 + original_value = "" + in_marker = 0 + next + } + in_dns { + cur_indent = indent_len($0) + if ($0 ~ /^[^[:space:]#][^:]*:/ && cur_indent <= dns_indent) { + flush_dns() + print + next + } + if ($0 ~ /^[[:space:]]*#[[:space:]]*>>>[[:space:]]*clashoo:dns_leak_protect_respect_rules[[:space:]]*$/) { + in_marker = 1 + if (child_indent < 0) child_indent = cur_indent + next + } + if (in_marker) { + if ($0 ~ /^[[:space:]]*#[[:space:]]*original-respect-rules:[[:space:]]*/) { + value = $0 + sub(/^[[:space:]]*#[[:space:]]*original-respect-rules:[[:space:]]*/, "", value) + original_value = trim_value(value) + } + if ($0 ~ /^[[:space:]]*#[[:space:]]*<<<[[:space:]]*clashoo:dns_leak_protect_respect_rules[[:space:]]*$/) + in_marker = 0 + next + } + if ($0 ~ /^[[:space:]]*respect-rules:[[:space:]]*/) { + if (original_value == "") { + value = $0 + sub(/^[[:space:]]*respect-rules:[[:space:]]*/, "", value) + original_value = trim_value(value) + } + if (child_indent < 0) child_indent = cur_indent + next + } + if ($0 ~ /^[[:space:]]*[^#[:space:]][^:]*:/ && cur_indent > dns_indent && child_indent < 0) + child_indent = cur_indent + dns_lines[++dns_n] = $0 + next + } + { print } + END { flush_dns() } + ' "$cfg" > "$tmp" && mv "$tmp" "$cfg" + rm -f "$tmp" 2>/dev/null +} + +# Use fallback DNS for queries; keep bootstrap DNS unchanged. +dns_mihomo_apply_leak_nameservers() { + local cfg="$1" enabled="${2:-0}" state="${1}.dns-leak-nameserver" + local selected="${1}.dns-leak-selected" tmp="${1}.$$" + # The config is rebuilt from the selected subscription on every run. + # A saved nameserver from an earlier run must never replace its DNS. + rm -f "$state" 2>/dev/null + [ "$enabled" = "1" ] || return 0 + [ -f "$cfg" ] || return 0 + command -v yq >/dev/null 2>&1 || return 0 + + yq e -r '.dns.fallback[]' "$cfg" > "$selected" 2>/dev/null || true + if [ ! -s "$selected" ]; then + printf '%s\n' 'https://1.1.1.1/dns-query' 'https://8.8.8.8/dns-query' > "$selected" + fi + + awk -v selected="$selected" ' + function indent_len(s) { match(s, /[^ ]/); return RSTART ? RSTART - 1 : length(s) } + function spaces(n, out,i) { + out = "" + for (i = 0; i < n; i++) out = out " " + return out + } + function print_selected(sp, line) { + print sp "nameserver:" + while ((getline line < selected) > 0) + if (line != "") print sp " - " line + close(selected) + inserted = 1 + } + /^dns:[[:space:]]*$/ { + in_dns = 1 + dns_indent = indent_len($0) + child_indent = -1 + print + next + } + in_dns && /^[^[:space:]#][^:]*:/ { + if (!inserted) print_selected(spaces(child_indent > 0 ? child_indent : dns_indent + 2)) + in_dns = 0 + print + next + } + in_dns && /^[[:space:]]*[^#[:space:]][^:]*:/ && child_indent < 0 { + child_indent = indent_len($0) + } + in_dns && /^[[:space:]]*nameserver:/ { + ns_indent = indent_len($0) + rest = $0 + sub(/^[[:space:]]*nameserver:[[:space:]]*/, "", rest) + sub(/[[:space:]]*(#.*)?$/, "", rest) + print_selected(spaces(ns_indent)) + if (rest == "") skip_ns = 1 + next + } + skip_ns { + cur_indent = indent_len($0) + if ($0 ~ /^[[:space:]]*$/ || cur_indent > ns_indent) next + skip_ns = 0 + } + { print } + END { + if (in_dns && !inserted) + print_selected(spaces(child_indent > 0 ? child_indent : dns_indent + 2)) + } + ' "$cfg" > "$tmp" && mv "$tmp" "$cfg" + rm -f "$selected" "$tmp" 2>/dev/null +} + + +# handle mihomo dns block: +# - strip previous clashoo:dns_leak_protect injection markers (incl. the old +# geosite:gfw block written by older versions) +# - rewrite the ipv6: line from ipv6_value (skip when empty) +# No longer injects fallback-filter.geosite:gfw — mihomo deprecated it and a +# GeoSite.dat missing the gfw category froze startup (issue #25). See flush_dns. +# (The fallback-filter parsing vars below are now vestigial; clean up separately.) +dns_mihomo_apply_leak_dns_block() { + local cfg="$1" enabled="${2:-0}" ipv6_value="${3:-}" tmp="${1}.$$" + [ -f "$cfg" ] || return 0 + + awk -v enabled="$enabled" -v ipv6_value="$ipv6_value" ' + function indent_len(s) { match(s, /[^ ]/); return RSTART ? RSTART - 1 : length(s) } + function spaces(n, out,i) { + out = "" + for (i = 0; i < n; i++) + out = out " " + return out + } + function flush_dns( i, sp, ff_sp) { + if (!in_dns) + return + if (in_ff) ff_end = dns_n + sp = spaces(child_indent > 0 ? child_indent : dns_indent + 2) + ff_sp = (ff_child_indent > 0 ? spaces(ff_child_indent) : sp " ") + + print dns_lines[1] + if (ipv6_value != "") + print sp "ipv6: " ipv6_value + # NOTE: clashoo used to inject `fallback-filter.geosite: [gfw]` here, but + # mihomo deprecated fallback-filter.geosite ("replace with nameserver-policy, + # will be removed") AND it hard-fails preflight + freezes startup (~90s) when + # the downloaded GeoSite.dat lacks the gfw category (issue #25). Stop + # injecting it. DNS-leak protection still works via respect-rules / leak + # nameservers / DST-PORT,853,REJECT handled elsewhere. The marker strip below + # still removes any gfw block injected by older versions, so disabling / + # upgrading cleans it up. + for (i = 2; i <= dns_n; i++) + print dns_lines[i] + in_dns = 0 + dns_n = 0 + } + + /^[[:space:]]*#[[:space:]]*>>>[[:space:]]*clashoo:dns_leak_protect[[:space:]]*$/ { + in_marker = 1 + next + } + in_marker { + if ($0 ~ /^[[:space:]]*#[[:space:]]*<<<[[:space:]]*clashoo:dns_leak_protect[[:space:]]*$/) + in_marker = 0 + next + } + + /^dns:[[:space:]]*$/ { + flush_dns() + in_dns = 1 + dns_indent = indent_len($0) + child_indent = -1 + dns_n = 1 + dns_lines[dns_n] = $0 + has_ff = 0 + in_ff = 0 + in_ff_geosite = 0 + ff_indent = -1 + ff_child_indent = -1 + ff_geosite_indent = -1 + ff_has_gfw = 0 + ff_end = -1 + next + } + in_dns { + cur_indent = indent_len($0) + if ($0 ~ /^[^[:space:]#][^:]*:/ && cur_indent <= dns_indent) { + flush_dns() + print + next + } + if ($0 ~ /^[[:space:]]*[^#[:space:]][^:]*:/ && cur_indent > dns_indent && child_indent < 0) + child_indent = cur_indent + if (in_ff && cur_indent <= ff_indent) { + ff_end = dns_n + in_ff = 0 + in_ff_geosite = 0 + } + if (in_ff_geosite && cur_indent <= ff_geosite_indent) + in_ff_geosite = 0 + if ($0 ~ /^[[:space:]]*fallback-filter:[[:space:]]*$/) { + has_ff = 1 + in_ff = 1 + ff_indent = cur_indent + ff_child_indent = -1 + } else if (in_ff && $0 ~ /^[[:space:]]*[^#[:space:]][^:]*:/ && ff_child_indent < 0) { + ff_child_indent = cur_indent + } + if (in_ff && $0 ~ /^[[:space:]]*geosite:[[:space:]]*$/) { + in_ff_geosite = 1 + ff_geosite_indent = cur_indent + } else if (in_ff_geosite && $0 ~ /^[[:space:]]*-[[:space:]]*gfw([[:space:]]*(#.*)?)?$/) { + ff_has_gfw = 1 + } + if ($0 ~ /^[[:space:]]*ipv6:[[:space:]]*/) + next + dns_lines[++dns_n] = $0 + next + } + { print } + END { flush_dns() } + ' "$cfg" > "$tmp" && mv "$tmp" "$cfg" + rm -f "$tmp" 2>/dev/null +} + +# handle DST-PORT,853,REJECT injection into rules block +# only remove Clashoo marker block, never user original rules +dns_mihomo_apply_leak_rule() { + local cfg="$1" enabled="${2:-0}" tmp="${1}.$$" has_user_853 + [ -f "$cfg" ] || return 0 + + has_user_853=$(awk ' + /^[[:space:]]*#[[:space:]]*>>>[[:space:]]*clashoo:dns_leak_protect_rule[[:space:]]*$/ { + in_marker = 1 + next + } + in_marker { + if ($0 ~ /^[[:space:]]*#[[:space:]]*<<<[[:space:]]*clashoo:dns_leak_protect_rule[[:space:]]*$/) + in_marker = 0 + next + } + /^[[:space:]]*-[[:space:]]*DST-PORT,853,REJECT([[:space:]]*(#.*)?)?$/ { found = 1 } + END { print found ? 1 : 0 } + ' "$cfg") + + awk -v enabled="$enabled" -v has_user_853="$has_user_853" ' + BEGIN { inserted = 0; saw_rules = 0 } + /^[[:space:]]*#[[:space:]]*>>>[[:space:]]*clashoo:dns_leak_protect_rule[[:space:]]*$/ { + in_marker = 1 + next + } + in_marker { + if ($0 ~ /^[[:space:]]*#[[:space:]]*<<<[[:space:]]*clashoo:dns_leak_protect_rule[[:space:]]*$/) + in_marker = 0 + next + } + /^rules:[[:space:]]*$/ { + saw_rules = 1 + print + if (enabled == "1" && has_user_853 != "1") { + print " # >>> clashoo:dns_leak_protect_rule" + print " - DST-PORT,853,REJECT" + print " # <<< clashoo:dns_leak_protect_rule" + inserted = 1 + } + next + } + { print } + END { + if (enabled == "1" && has_user_853 != "1" && !inserted && !saw_rules) { + print "" + print "rules:" + print " # >>> clashoo:dns_leak_protect_rule" + print " - DST-PORT,853,REJECT" + print " # <<< clashoo:dns_leak_protect_rule" + } + } + ' "$cfg" > "$tmp" && mv "$tmp" "$cfg" + rm -f "$tmp" 2>/dev/null +} + +dns_mihomo_apply_leak_protect() { + local cfg="$1" enabled="${2:-0}" ipv6_value="${3:-}" + [ -f "$cfg" ] || return 0 + dns_mihomo_apply_leak_nameservers "$cfg" "$enabled" + dns_mihomo_apply_respect_rules "$cfg" "$enabled" + dns_mihomo_apply_leak_dns_block "$cfg" "$enabled" "$ipv6_value" + dns_mihomo_apply_leak_rule "$cfg" "$enabled" +} diff --git a/clashoo/files/usr/share/clashoo/runtime/dns_loopback_adapter.yq b/clashoo/files/usr/share/clashoo/runtime/dns_loopback_adapter.yq new file mode 100644 index 00000000..12d5851e --- /dev/null +++ b/clashoo/files/usr/share/clashoo/runtime/dns_loopback_adapter.yq @@ -0,0 +1,17 @@ +. as $document | +strenv(CLASHOO_DNS_OLD_PORT) as $old_port | +strenv(CLASHOO_DNS_HOST_PATTERN) as $host_pattern | +(strenv(CLASHOO_DNS_REPLACEMENT) | split(" ") | map(select(length > 0))) as $replacement | +("(?i)^(?:[a-z][a-z0-9+.-]*://)?(?:" + $host_pattern + "):" + $old_port + "(?:$|[/#?])") as $old_loopback | +( + $document | + select((.dns."proxy-server-nameserver" | type) == "!!seq") | + ([.dns."proxy-server-nameserver"[] | select(type == "!!str" and test($old_loopback))] | length) as $match_count | + select($match_count > 0) | + .dns."proxy-server-nameserver" = + ([ + .dns."proxy-server-nameserver"[] | + select((type == "!!str" and test($old_loopback)) | not) | + select(. as $server | ($replacement | contains([$server])) | not) + ] + $replacement) +) // $document diff --git a/clashoo/files/usr/share/clashoo/runtime/dns_node_policy.uc b/clashoo/files/usr/share/clashoo/runtime/dns_node_policy.uc new file mode 100644 index 00000000..aa523a09 --- /dev/null +++ b/clashoo/files/usr/share/clashoo/runtime/dns_node_policy.uc @@ -0,0 +1,118 @@ +#!/usr/bin/ucode +'use strict'; + +import { readfile } from 'fs'; + +// Mirror node-domain DNS policies from the subscription into +// proxy-server-nameserver-policy (issue #73). +// +// Airports publish their own resolver for node domains, e.g. +// nameserver-policy: +// "+.chylink.xyz": [tls://112.74.48.57:10853] +// which answers with domestic relay IPs. mihomo resolves *proxy server* domains +// through proxy-server-nameserver(-policy) and ignores nameserver-policy there, +// so that resolver is never used and node domains fall back to public DNS - +// which may return overseas CDN IPs that some lines cannot reach at all, leaving +// every node (AnyTLS is TCP-only) timing out. +// +// Input : CLASHOO_NODE_DNS_SOURCE_FILE = path to {"policies": {...}, "servers": [...]} +// (CLASHOO_NODE_DNS_SOURCE with the JSON itself is still accepted) +// Output: JSON map with only the policy entries matching a node server domain +// (nothing is printed when there is no match) + +let source = ''; +let source_file = getenv('CLASHOO_NODE_DNS_SOURCE_FILE') || ''; + +if (length(source_file)) { + try { + source = readfile(source_file) || ''; + } + catch (e) { + exit(0); + } +} +else { + source = getenv('CLASHOO_NODE_DNS_SOURCE') || ''; +} + +if (!length(source)) + exit(0); + +let parsed; + +try { + parsed = json(source); +} +catch (e) { + exit(0); +} + +if (type(parsed) != 'object') + exit(0); + +let policies = parsed.policies; +let servers = parsed.servers; + +if (type(policies) != 'object' || type(servers) != 'array') + exit(0); + +let domains = []; +let seen = {}; + +for (let server in servers) { + if (type(server) != 'string' || !length(server)) + continue; + server = lc(server); + if (seen[server]) + continue; + seen[server] = true; + push(domains, server); +} + +if (!length(domains)) + exit(0); + +// suffix match on a label boundary: node.example.com matches +.example.com +function domain_matches(key, domain) { + let key_len = length(key); + let domain_len = length(domain); + + if (!key_len || domain_len < key_len) + return false; + if (domain == key) + return true; + if (substr(domain, domain_len - key_len) != key) + return false; + return substr(domain, domain_len - key_len - 1, 1) == '.'; +} + +let result = {}; + +for (let key, value in policies) { + if (type(key) != 'string' || !length(key)) + continue; + + let lowered = lc(key); + let is_pattern = match(lowered, /^[+*]?\./) != null; + let domain = replace(lowered, /^\+\./, ''); + + domain = replace(domain, /^\*\./, ''); + domain = replace(domain, /^\./, ''); + + let matched = false; + + for (let candidate in domains) { + if (is_pattern ? domain_matches(domain, candidate) : (candidate == lowered)) { + matched = true; + break; + } + } + + if (matched) + result[key] = value; +} + +if (!length(keys(result))) + exit(0); + +print(sprintf('%J', result)); diff --git a/clashoo/files/usr/share/clashoo/runtime/iprules.sh b/clashoo/files/usr/share/clashoo/runtime/iprules.sh new file mode 100755 index 00000000..9f2e327c --- /dev/null +++ b/clashoo/files/usr/share/clashoo/runtime/iprules.sh @@ -0,0 +1,98 @@ +#!/bin/sh +. /lib/functions.sh + + +TMP_PREFIX="/tmp/clashoo_iprules.$$" +CUSTOM_RULE_FILE="${TMP_PREFIX}.ipadd.conf" +RULE="${TMP_PREFIX}.rules_conf.yaml" +CLASH="${TMP_PREFIX}.conf.yaml" +CONFIG_YAML="${CLASHOO_CONFIG_YAML:-/etc/clashoo/config.yaml}" +CLASH_CONFIG="${TMP_PREFIX}.config.yaml" +trap 'rm -f "$CUSTOM_RULE_FILE" "$RULE" "$CLASH" "$CLASH_CONFIG"' EXIT + +[ -f "$CONFIG_YAML" ] || exit 0 + +. /usr/share/clashoo/runtime/primary_group.sh + + +# __PROXY__ placeholder -> primary proxy group (cached by RPC); +# fallback to GLOBAL (built-in mihomo group) +CACHED_PRIMARY_GROUP="$(uci -q get clashoo.config.primary_proxy_group)" +PRIMARY_GROUP="$(clashoo_resolve_primary_group "$CONFIG_YAML" "$CACHED_PRIMARY_GROUP")" +[ -n "$PRIMARY_GROUP" ] || PRIMARY_GROUP="GLOBAL" + +append=$(uci get clashoo.config.append_rules 2>/dev/null) +if [ "${append:-0}" -eq 1 ];then + + if [ -f $CLASH_CONFIG ];then + rm -rf $CLASH_CONFIG 2>/dev/null + fi + + cp $CONFIG_YAML $CLASH_CONFIG 2>/dev/null + if [ ! -z "$(grep "^Rule:" "$CLASH_CONFIG")" ]; then + sed -i "/^Rule:/i\#RULESTART#" $CLASH_CONFIG 2>/dev/null + elif [ ! -z "$(grep "^rules:" "$CLASH_CONFIG")" ]; then + sed -i "/^rules:/i\#RULESTART#" $CLASH_CONFIG 2>/dev/null + fi + sed -i -e "\$a#RULEEND#" $CLASH_CONFIG 2>/dev/null + + awk '/#RULESTART#/,/#RULEEND#/{print}' "$CLASH_CONFIG" 2>/dev/null |sed "s/\'//g" 2>/dev/null |sed 's/\"//g' 2>/dev/null |sed 's/\t/ /g' 2>/dev/null |grep '^ \{0,\}- '|awk -F '- ' '{print "- "$2}' | sed 's/^ \{0,\}//' 2>/dev/null |sed 's/ \{0,\}$//' 2>/dev/null >$RULE 2>&1 + + sed -i '/#RULESTART#/,/#RULEEND#/d' "$CLASH_CONFIG" 2>/dev/null + + sed -i -e "\$a " $CLASH_CONFIG 2>/dev/null + sed -i "1i\rules:" $RULE 2>/dev/null + cat $CLASH_CONFIG $RULE >$CLASH 2>/dev/null + mv $CLASH $CONFIG_YAML 2>/dev/null + rm -f $RULE $CLASH_CONFIG 2>/dev/null +fi + +# ===== custom routing rules (UCI: config addtype), decoupled ===== +# fields: type(DOMAIN|SUFFIX|KEYWORD|IP-CIDR), ipaaddr, pgroup, res(no-resolve) +# pgroup: DIRECT or __PROXY__ (resolved at injection) +rm -f "$CUSTOM_RULE_FILE" 2>/dev/null + +ipadd() +{ + local section="$1" + config_get "pgroup" "$section" "pgroup" "" + config_get "ipaaddr" "$section" "ipaaddr" "" + config_get "type" "$section" "type" "" + config_get "res" "$section" "res" "" + + [ -z "$type" ] && return + [ -z "$ipaaddr" ] && return + [ -z "$pgroup" ] && return + + [ "$pgroup" = "__PROXY__" ] && pgroup="$PRIMARY_GROUP" + + # quote whole rule (group name may contain spaces/emoji) + # so mixed indent with subscription rules does not break YAML parsing + if [ "${res}" = "1" ];then + echo "${RULE_INDENT}- \"$type,$ipaaddr,$pgroup,no-resolve\"" >> "$CUSTOM_RULE_FILE" + else + echo "${RULE_INDENT}- \"$type,$ipaaddr,$pgroup\"" >> "$CUSTOM_RULE_FILE" + fi +} + +# detect existing rules indent for alignment (default 2) +RULE_INDENT="$(awk '/^[[:space:]]*rules:/{f=1;next} f&&/^[[:space:]]*-/{match($0,/^[[:space:]]*/);print substr($0,1,RLENGTH);exit}' "$CONFIG_YAML" 2>/dev/null)" +[ -n "$RULE_INDENT" ] || RULE_INDENT=" " + +config_load "clashoo" +config_foreach ipadd "addtype" + +# clean previous injection block (idempotent) +sed -i '/#CUSTOMRULESTART#/,/#CUSTOMRULEEND#/d' "$CONFIG_YAML" 2>/dev/null + +if [ -f "$CUSTOM_RULE_FILE" ];then + sed -i -e "\$a#CUSTOMRULEEND#" "$CUSTOM_RULE_FILE" 2>/dev/null + if [ ! -z "$(grep "^ \{0,\}rules:" "$CONFIG_YAML")" ]; then + # 插在 rules: 段最前面,优先于订阅自带规则命中(纠错) + sed -i '/^ \{0,\}rules:/a\#CUSTOMRULESTART#' "$CONFIG_YAML" 2>/dev/null + else + echo "rules:" >> "$CONFIG_YAML" 2>/dev/null + echo "#CUSTOMRULESTART#" >> "$CONFIG_YAML" 2>/dev/null + fi + sed -i "/#CUSTOMRULESTART#/r${CUSTOM_RULE_FILE}" "$CONFIG_YAML" 2>/dev/null +fi diff --git a/clashoo/files/usr/share/clashoo/runtime/mixin.uc b/clashoo/files/usr/share/clashoo/runtime/mixin.uc new file mode 100644 index 00000000..6ace606f --- /dev/null +++ b/clashoo/files/usr/share/clashoo/runtime/mixin.uc @@ -0,0 +1,339 @@ +#!/usr/bin/ucode +'use strict'; + +import { cursor } from 'uci'; +const uci = cursor(); + +function b(v) { + if (v == null || v == '' || v == '0' || v == 'false') return false; + if (v == '1' || v == 'true') return true; + return v; +} +function i(v, d) { let n = v != null ? int(v) : null; return n != null ? n : d; } +function s(v, d) { let t = trim(v || ''); return length(t) ? t : d; } +function a(k) { return uci.get('clashoo', 'config', k); } +function ab(k) { return b(a(k)); } + +const cfg = {}; + +/* ── 端口与基本设置 ─────────────────────────────────────── */ +let v; +v = i(a('http_port')); if (v != null) cfg['port'] = v; +v = i(a('socks_port')); if (v != null) cfg['socks-port'] = v; +v = i(a('redir_port')); if (v != null) cfg['redir-port'] = v; +v = i(a('mixed_port')); if (v != null) cfg['mixed-port'] = v; +v = i(a('tproxy_port')); if (v != null) cfg['tproxy-port'] = v; + +v = a('bind_addr'); +if (!v || v == '*') v = null; +if (v) cfg['bind-address'] = v; + +v = a('p_mode'); +if (v == 'script') v = 'rule'; +if (v) cfg['mode'] = v; + +cfg['log-level'] = s(a('level'), 'info'); +cfg['allow-lan'] = ab('allow_lan'); +cfg['ipv6'] = ab('enable_ipv6'); +cfg['routing-mark'] = 6666; +cfg['external-controller'] = '0.0.0.0:' + i(a('dash_port'), 9090); +cfg['external-ui'] = './dashboard'; +cfg['secret'] = s(a('dash_pass'), ''); + +v = a('interf'); +if (v && v != '0') cfg['interface-name'] = v; + +/* ── TUN ───────────────────────────────────────────────── */ +let tun_mode = i(a('tun_mode'), 0); +let tcp_mode = s(a('tcp_mode'), 'redirect'); +let udp_mode = s(a('udp_mode'), 'tproxy'); +let tun_enabled = (tun_mode == 1 || tcp_mode == 'tun' || udp_mode == 'tun'); +let acl_enabled = false; +uci.foreach('clashoo', 'lan_acl', function(sec) { + if (b(sec.enabled) != false) acl_enabled = true; +}); +if (!ab('acl_migrated')) { + let access_control = i(a('access_control'), 0); + acl_enabled = access_control == 1 || access_control == 2; +} +let tun_acl = tun_enabled && acl_enabled; + +cfg['tun'] = { + enable: tun_enabled, + stack: s(a('stack'), 'gvisor'), + 'auto-route': true, + 'auto-redirect': !tun_acl, + 'auto-detect-interface': true, +}; +let tun_mtu = a('tun_mtu'); +if (tun_mtu != null) cfg['tun']['mtu'] = int(tun_mtu); +if (ab('tun_gso')) { + cfg['tun']['gso'] = true; + cfg['tun']['gso-max-size'] = i(a('tun_gso_max_size'), 65536); +} +if (ab('tun_dns_hijack')) { + cfg['tun']['dns-hijack'] = ['any:53', 'tcp://any:53']; +} +if (!tun_enabled) { + cfg['tun']['enable'] = false; +} + +/* ── DNS ───────────────────────────────────────────────── */ +let dns_port = i(a('listen_port'), 1053); +cfg['dns'] = { + enable: b(a('enable_dns')) != false, + listen: '0.0.0.0:' + dns_port, + 'enhanced-mode': s(a('enhanced_mode'), 'fake-ip'), + 'fake-ip-range': s(a('fake_ip_range'), '198.18.0.1/16'), + 'fake-ip-filter': [], + ipv6: ab('enable_ipv6'), +}; +if (cfg['dns']['enhanced-mode'] == 'fake-ip' && ab('enable_ipv6')) + cfg['dns']['fake-ip-range6'] = s(a('fake_ip_range6'), 'fc00::/18'); +/* fake-ip-filter-mode: blacklist (default) | whitelist | rule */ +let filter_mode = s(a('fake_ip_filter_mode'), 'blacklist'); +if (filter_mode != 'blacklist') cfg['dns']['fake-ip-filter-mode'] = filter_mode; + +/* map geosite:cn to bundled cn.mrs rule-set so mihomo skips the 10MB geosite.dat */ +let need_cn_rs = false; +let keep_fakeip = length(s(getenv('CLASHOO_KEEP_FAKEIP_FILTER'), '')); +if (keep_fakeip) { + delete cfg['dns']['fake-ip-filter']; +} else { + let push_filter = function(f) { + f = trim(f || ''); + if (!length(f)) return; + if (filter_mode == 'rule') { + if (match(f, /,(fake-ip|real-ip)$/)) { push(cfg['dns']['fake-ip-filter'], f); return; } + if (index(f, ',') >= 0) { push(cfg['dns']['fake-ip-filter'], f + ',real-ip'); return; } + if (f == 'geosite:cn') { f = 'RULE-SET,cn_domain,real-ip'; need_cn_rs = true; } + else if (match(f, /^geosite:/)) { f = 'GEOSITE,' + substr(f, 8) + ',real-ip'; } + else if (match(f, /^rule-set:/)) { + let rs = substr(f, 9); + if (rs == 'cn_domain') need_cn_rs = true; + f = 'RULE-SET,' + rs + ',real-ip'; + } + else if (match(f, /^\+\./)) { f = 'DOMAIN-SUFFIX,' + substr(f, 2) + ',real-ip'; } + else if (match(f, /\*/)) { f = 'DOMAIN-WILDCARD,' + f + ',real-ip'; } + else { f = 'DOMAIN,' + f + ',real-ip'; } + } else { + if (f == 'geosite:cn') { f = 'rule-set:cn_domain'; need_cn_rs = true; } + else if (f == 'rule-set:cn_domain') need_cn_rs = true; + } + push(cfg['dns']['fake-ip-filter'], f); + }; + let filters = a('fake_ip_filter'); + if (type(filters) == 'array') { for (let f in filters) push_filter(f); } + else if (filters != null) push_filter(filters); +} + +/* fallback-filter(默认 geoip:false,防止冷启动依赖 MMDB) */ +cfg['dns']['fallback-filter'] = { geoip: ab('fallback_filter_geoip') }; + +let dns_present = {}; +for (let f in split(s(getenv('CLASHOO_DNS_PRESENT'), ''), ',')) + if (length(trim(f))) dns_present[trim(f)] = true; +let dns_force = {}; +for (let f in split(s(getenv('CLASHOO_DNS_FORCE'), ''), ',')) + if (length(trim(f))) dns_force[trim(f)] = true; +let dns_inherit_mode = s(getenv('CLASHOO_DNS_INHERIT_MODE'), ''); +let inherited_nameserver = json(s(getenv('CLASHOO_DNS_INHERIT_NAMESERVER'), '[]')); +if (type(inherited_nameserver) != 'array') { + if (inherited_nameserver != null && inherited_nameserver != '') + inherited_nameserver = [inherited_nameserver]; + else + inherited_nameserver = []; +} +let dns_role_fields = ['nameserver', 'proxy-server-nameserver', 'direct-nameserver', + 'default-nameserver', 'nameserver-policy', 'proxy-server-nameserver-policy', 'respect-rules']; +let had_user_dns_roles = false; +for (let f in dns_role_fields) + if (dns_present[f]) had_user_dns_roles = true; +if (length(keys(dns_force)) || !had_user_dns_roles) + cfg['dns']['x-clashoo-managed-dns'] = true; + +function dns_scheme(protocol) { + switch (trim(protocol || '')) { + case '': + case 'none': return ''; + case 'udp': case 'udp://': return 'udp://'; + case 'tcp': case 'tcp://': return 'tcp://'; + case 'dot': case 'tls': case 'tls://': return 'tls://'; + case 'doh': case 'https': case 'https://': return 'https://'; + case 'doq': case 'quic': case 'quic://': return 'quic://'; + default: return protocol; + } +} + +function dns_server(address, protocol, port) { + let addr = trim(address || ''); + if (!length(addr)) return null; + if (match(addr, /^[A-Za-z][A-Za-z0-9+.-]*:\/\//)) return addr; + let prefix = dns_scheme(protocol); + return length(trim(port || '')) ? prefix + addr + ':' + trim(port) : prefix + addr; +} + +let dns_roles = {}; +uci.foreach('clashoo', 'dnsservers', function(sec) { + if (b(sec.enabled) == false) return; + let role = s(sec.ser_type, 'nameserver'); + if (role == 'fallback' && cfg['dns']['enhanced-mode'] == 'fake-ip') return; + let srv = dns_server(sec.ser_address, sec.protocol, sec.ser_port); + if (!srv) return; + if (!dns_roles[role]) dns_roles[role] = []; + push(dns_roles[role], srv); +}); +for (let role in keys(dns_roles)) { + if (role == 'proxy-server-nameserver' && (dns_inherit_mode == 'inherit' || dns_inherit_mode == 'skip') && + !dns_present[role] && !dns_force[role]) continue; + if (!dns_present[role] || dns_force[role]) cfg['dns'][role] = dns_roles[role]; +} + +if (dns_inherit_mode == 'inherit' && length(inherited_nameserver) && + !dns_present['proxy-server-nameserver'] && !dns_force['proxy-server-nameserver']) + cfg['dns']['proxy-server-nameserver'] = inherited_nameserver; + +let bootstrap = a('default_nameserver'); +if (bootstrap == null) bootstrap = a('defaul_nameserver'); +if (type(bootstrap) != 'array') bootstrap = bootstrap != null ? [bootstrap] : []; +if (length(bootstrap) && (!dns_present['default-nameserver'] || dns_force['default-nameserver'])) + cfg['dns']['default-nameserver'] = bootstrap; + +let policies = {}; +let proxy_policies = {}; +uci.foreach('clashoo', 'dns_policy', function(sec) { + if (b(sec.enabled) == false) return; + let role = s(sec.policy_type, 'nameserver-policy'); + if (role != 'nameserver-policy' && role != 'proxy-server-nameserver-policy') return; + let matcher = trim(sec.matcher || ''); + let servers = sec.nameserver; + if (type(servers) != 'array') servers = servers != null ? [servers] : []; + if (!length(matcher) || !length(servers)) return; + if (matcher == 'geosite:cn') { matcher = 'rule-set:cn_domain'; need_cn_rs = true; } + if (role == 'nameserver-policy') policies[matcher] = servers; + else proxy_policies[matcher] = servers; +}); +if (length(keys(policies)) && (!dns_present['nameserver-policy'] || dns_force['nameserver-policy'])) + cfg['dns']['nameserver-policy'] = policies; +if (length(keys(proxy_policies)) && (!dns_present['proxy-server-nameserver-policy'] || dns_force['proxy-server-nameserver-policy'])) + cfg['dns']['proxy-server-nameserver-policy'] = proxy_policies; + +let respect_rules = a('dns_respect_rules') == null ? true : ab('dns_respect_rules'); +if (dns_force['respect-rules']) + cfg['dns']['respect-rules'] = respect_rules; +else if (respect_rules && length(cfg['dns']['proxy-server-nameserver'] || []) + && !dns_present['respect-rules'] && !dns_present['prefer-h3']) + cfg['dns']['respect-rules'] = true; + +/* profile */ +let store_selected = ab('selection_cache'); +/* fake-ip cache defaults ON to survive restarts; opt out with '0' */ +let store_fake = a('fake_ip_cache') == null ? true : ab('fake_ip_cache'); +if (store_selected || store_fake) { + cfg['profile'] = {}; + if (store_selected) cfg['profile']['store-selected'] = true; + if (store_fake) cfg['profile']['store-fake-ip'] = true; +} + +/* bundled cn.mrs rule-provider, referenced by fake-ip-filter rule-set:cn_domain */ +if (need_cn_rs) { + if (!cfg['rule-providers']) cfg['rule-providers'] = {}; + cfg['rule-providers']['cn_domain'] = { + type: 'file', + path: './ruleset/cn.mrs', + format: 'mrs', + behavior: 'domain', + }; +} + + + +/* ── authentication ────────────────────────────────── */ +if (ab('authentication')) { + cfg['authentication'] = []; + uci.foreach('clashoo', 'authentication', function(sec) { + if (b(sec.enabled) == false) return; + let user = trim(sec.username || ''); + let pass = trim(sec.password || ''); + if (user) push(cfg['authentication'], user + ':' + pass); + }); +} + +/* ── hosts ────────────────────────────────────────── */ +cfg['hosts'] = {}; +uci.foreach('clashoo', 'hosts', function(sec) { + if (b(sec.enabled) == false) return; + let domain = trim(sec.adress || ''); + let ip = trim(sec.ip || ''); + if (domain && ip) cfg['hosts'][domain] = ip; +}); +if (length(keys(cfg['hosts'])) == 0) delete cfg['hosts']; + +/* ── sniffer ──────────────────────────────────────── */ +if (ab('sniffer_streaming') && !length(s(getenv('CLASHOO_KEEP_SNIFFER'), ''))) { + cfg['sniffer'] = { + enable: true, + 'force-dns-mapping': true, + 'parse-pure-ip': true, + sniff: { + HTTP: { ports: [80, 8080], 'override-destination': true }, + TLS: { ports: [443, 8443], 'override-destination': true }, + QUIC: { ports: [443, 8443], 'override-destination': true }, + }, + 'force-domain': [ + '+.youtube.com', + '+.googlevideo.com', + '+.netflix.com', + '+.nflxvideo.net', + '+.disneyplus.com', + '+.hulu.com', + '+.hbomax.com', + ], + 'skip-domain': [ + '+.lan', + '+.local', + ], + }; +} + +/* ── Smart kernel injection ───────────────────────── */ +if (ab('smart_auto_switch')) { + /* Smart 策略注入:通过 proxy-groups 覆盖实现 */ +} + +/* ── ECS ──────────────────────────────────────────── */ +let ecs = s(a('dns_ecs')); +if (ecs) cfg['dns']['edns-client-subnet'] = ecs; + +/* ── cache_file / experimental ──────────────────── */ +cfg['experimental'] = { + 'cache_file': { enabled: true }, + 'clash_api': { + 'external-controller': '0.0.0.0:' + i(a('dash_port'), 9090), + 'external-ui': './dashboard', + 'secret': s(a('dash_pass'), ''), + }, +}; + + +/* ── 输出 JSON (不含 null/空对象) ─────────────────── */ +function clean(obj) { + if (type(obj) == 'array') { + let r = []; + for (let e in obj) { + e = clean(e); + if (e != null) push(r, e); + } + return length(r) ? r : null; + } + if (type(obj) == 'object') { + let r = {}; + for (let k in keys(obj)) { + let v = clean(obj[k]); + if (v != null) r[k] = v; + } + return length(keys(r)) ? r : null; + } + return obj; +} +print(clean(cfg)); diff --git a/clashoo/files/usr/share/clashoo/runtime/primary_group.sh b/clashoo/files/usr/share/clashoo/runtime/primary_group.sh new file mode 100755 index 00000000..997e941b --- /dev/null +++ b/clashoo/files/usr/share/clashoo/runtime/primary_group.sh @@ -0,0 +1,49 @@ +#!/bin/sh + +clashoo_resolve_primary_group() { + local config_yaml="$1" + local cached_group="$2" + local group_rows + local resolved_group + + if [ "$cached_group" = "GLOBAL" ]; then + printf '%s\n' "GLOBAL" + return 0 + fi + + if [ ! -f "$config_yaml" ] || ! command -v yq >/dev/null 2>&1; then + printf '%s\n' "GLOBAL" + return 0 + fi + + group_rows="$(yq -M e -r '(."proxy-groups" // [])[] | [.name, .type] | @tsv' "$config_yaml" 2>/dev/null)" || { + printf '%s\n' "GLOBAL" + return 0 + } + + if [ -n "$cached_group" ] && printf '%s\n' "$group_rows" | + awk -F '\t' -v wanted="$cached_group" '$1 == wanted { found = 1; exit } END { exit !found }' + then + printf '%s\n' "$cached_group" + return 0 + fi + + resolved_group="$(printf '%s\n' "$group_rows" | awk -F '\t' ' + tolower($2) == "select" && + (index($1, "节点选择") || index($1, "代理") || index($1, "🚀") || tolower($1) ~ /proxy/) { + print $1 + exit + } + ')" + + if [ -z "$resolved_group" ]; then + resolved_group="$(printf '%s\n' "$group_rows" | awk -F '\t' ' + tolower($2) == "select" { + print $1 + exit + } + ')" + fi + + printf '%s\n' "${resolved_group:-GLOBAL}" +} diff --git a/clashoo/files/usr/share/clashoo/runtime/quic_fallback_guard.yq b/clashoo/files/usr/share/clashoo/runtime/quic_fallback_guard.yq new file mode 100644 index 00000000..59ee6074 --- /dev/null +++ b/clashoo/files/usr/share/clashoo/runtime/quic_fallback_guard.yq @@ -0,0 +1,9 @@ +# If every configured rule points to a proxy without UDP support, mihomo falls +# through to DIRECT. Reject only that otherwise-unmatched QUIC traffic so the +# client can retry over TCP; rules with a usable UDP target still win first. +with( + select(has("rules")) | + select((.rules | type) == "!!seq") | + select(([.rules[] | select(. == "AND,((NETWORK,UDP),(DST-PORT,443)),REJECT")] | length) == 0); + .rules += ["AND,((NETWORK,UDP),(DST-PORT,443)),REJECT"] +) diff --git a/clashoo/files/usr/share/clashoo/runtime/restore.sh b/clashoo/files/usr/share/clashoo/runtime/restore.sh new file mode 100755 index 00000000..f5dd17f4 --- /dev/null +++ b/clashoo/files/usr/share/clashoo/runtime/restore.sh @@ -0,0 +1,37 @@ +#!/bin/sh + +if [ -f /usr/share/clashbackup/history ];then + +HISTORY_PATH="/usr/share/clashbackup/history" +SECRET=$(uci get clashoo.config.dash_pass 2>/dev/null) +LAN_IP=$(uci get network.lan.ipaddr 2>/dev/null |awk -F '/' '{print $1}' 2>/dev/null) +PORT=$(uci get clashoo.config.dash_port 2>/dev/null) +urlencode() { + if [ "$#" != 1 ]; then + return 1 + fi + printf '%s' "$1" | hexdump -v -e '1/1 "%u "' | awk ' + { + for (i = 1; i <= NF; i++) { + b = $i + 0 + if ((b >= 48 && b <= 57) || (b >= 65 && b <= 90) || + (b >= 97 && b <= 122) || b == 45 || b == 46 || + b == 95 || b == 126) + printf "%c", b + else + printf "%%%02X", b + } + }' +} +cat $HISTORY_PATH |while read line +do + if [ -z "$(echo $line |grep "#*#")" ]; then + continue + else + GORUP_NAME=$(urlencode "$(echo $line |awk -F '#*#' '{print $1}')") + NOW_NAME=$(echo $line |awk -F '#*#' '{print $3}') + curl -H "Authorization: Bearer ${SECRET}" -H "Content-Type:application/json" -X PUT -d '{"name":"'"$NOW_NAME"'"}' http://"$LAN_IP":"$PORT"/proxies/"$GORUP_NAME" >/dev/null 2>&1 + fi +done >/dev/null 2>&1 +curl -m 5 --retry 2 -H "Authorization: Bearer ${SECRET}" -H "Content-Type:application/json" -X DELETE http://"$LAN_IP":"$PORT"/connections >/dev/null 2>&1 +fi diff --git a/clashoo/files/usr/share/clashoo/runtime/sniffer_overrides.yq b/clashoo/files/usr/share/clashoo/runtime/sniffer_overrides.yq new file mode 100644 index 00000000..885d81af --- /dev/null +++ b/clashoo/files/usr/share/clashoo/runtime/sniffer_overrides.yq @@ -0,0 +1,28 @@ +# Preserve every explicit user choice while restoring missing per-protocol defaults. +with( + .sniffer | + select((has("enable") | not) or (.enable == true)) | + select((has("override-destination") | not) or (."override-destination" == true)); + with( + select(has("force-dns-mapping") | not); + ."force-dns-mapping" = true + ) | + with( + select(has("parse-pure-ip") | not); + ."parse-pure-ip" = true + ) | + with( + .sniff | select(has("TLS")); + with( + .TLS | select(type == "!!map" and (has("override-destination") | not)); + ."override-destination" = true + ) + ) | + with( + .sniff | select(has("QUIC")); + with( + .QUIC | select(type == "!!map" and (has("override-destination") | not)); + ."override-destination" = true + ) + ) +) diff --git a/clashoo/files/usr/share/clashoo/server.list b/clashoo/files/usr/share/clashoo/server.list new file mode 100755 index 00000000..23dfb38d --- /dev/null +++ b/clashoo/files/usr/share/clashoo/server.list @@ -0,0 +1,63 @@ +time.windows.com +time.nist.gov +time.apple.com +time.asia.apple.com +cn.ntp.org.cn +edu.ntp.org.cn +hk.ntp.org.cn +tw.ntp.org.cn +us.ntp.org.cn +sgp.ntp.org.cn +kr.ntp.org.cn +jp.ntp.org.cn +de.ntp.org.cn +ina.ntp.org.cn +0.openwrt.pool.ntp.org +1.openwrt.pool.ntp.org +2.openwrt.pool.ntp.org +3.openwrt.pool.ntp.org +ntp.aliyun.com +ntp1.aliyun.com +ntp2.aliyun.com +ntp3.aliyun.com +ntp4.aliyun.com +ntp5.aliyun.com +ntp6.aliyun.com +ntp7.aliyun.com +time1.aliyun.com +time2.aliyun.com +time3.aliyun.com +time4.aliyun.com +time5.aliyun.com +time6.aliyun.com +time7.aliyun.com +s1c.time.edu.cn +s2m.time.edu.cn +s1b.time.edu.cn +s1e.time.edu.cn +s2a.time.edu.cn +s2b.time.edu.cn +time1.apple.com +time2.apple.com +time3.apple.com +time4.apple.com +time5.apple.com +time6.apple.com +time7.apple.com +time1.google.com +time2.google.com +time3.google.com +time4.google.com +.music.163.com +.music.126.net +man.netease.com +api.iplay.163.com +ac.dun.163yun.com +mr.da.netease.com +crash.163.com +imap.163.com +msftconnecttest.com +msftncsi.com +captive.qq.com +ssl.ptlogin2.qq.com +login.qq.com diff --git a/clashoo/files/usr/share/clashoo/update/component_update.sh b/clashoo/files/usr/share/clashoo/update/component_update.sh new file mode 100755 index 00000000..ba0b92bc --- /dev/null +++ b/clashoo/files/usr/share/clashoo/update/component_update.sh @@ -0,0 +1,649 @@ +#!/bin/sh + +LOG_FILE="/tmp/clashoo_component_update.log" +RUN_FILE="/var/run/clashoo_component_update" +STATE_FILE="/tmp/clashoo_component_update_state" +TMP_DIR="/tmp/clashoo-component-update" +APK_UPGRADE_FLAG="/var/run/clashoo_package_upgrade" +FEED_BASE_URL="https://down.dllkids.xyz/openwrt-feed/clashoo" +GITHUB_API_URL="https://api.github.com/repos/kenzok8/openwrt-clashoo/releases/latest" +ENV_GITHUB_PROXY_PREFIX="${GITHUB_PROXY_PREFIX:-}" +UCI_GITHUB_PROXY_PREFIX="$(uci -q get clashoo.config.core_mirror_prefix 2>/dev/null)" +GITHUB_PROXY_PREFIX="${UCI_GITHUB_PROXY_PREFIX:-${ENV_GITHUB_PROXY_PREFIX:-https://ghfast.top/}}" +case "$GITHUB_PROXY_PREFIX" in + */) ;; + *) GITHUB_PROXY_PREFIX="${GITHUB_PROXY_PREFIX}/" ;; +esac +CONNECT_TIMEOUT="${CONNECT_TIMEOUT:-8}" +REQUEST_TIMEOUT="${REQUEST_TIMEOUT:-20}" +LOW_SPEED_TIME="${LOW_SPEED_TIME:-30}" +LOW_SPEED_LIMIT="${LOW_SPEED_LIMIT:-1024}" + +trap 'rm -f "$APK_UPGRADE_FLAG"' EXIT + +log() { + mkdir -p "$(dirname "$LOG_FILE")" "$(dirname "$STATE_FILE")" >/dev/null 2>&1 + printf '%s - %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" >>"$LOG_FILE" + printf 'component=%s\nstatus=%s\nmessage=%s\n' "${COMPONENT:-}" "${STATUS:-running}" "$*" >"$STATE_FILE" +} + +finish() { + rc="$1" + msg="$2" + STATUS="success" + [ "$rc" -eq 0 ] || STATUS="failed" + log "$msg" + rm -f "$RUN_FILE" "$APK_UPGRADE_FLAG" + exit "$rc" +} + +# In kernel-only mode there is no transparent proxy, so component downloads +# would go out direct and stall behind the GFW. Route them through the running +# core (shared logic in proxy_lib.sh). Normal mode returns empty -> TPROXY. +# Guard the source: on a stale install missing proxy_lib.sh, an unguarded `.` +# under `set -e` kills the whole update silently (empty log, nothing happens). +# Fall back to no proxy detection (direct / TPROXY) instead of dying. +if [ -f /usr/share/clashoo/update/proxy_lib.sh ]; then + . /usr/share/clashoo/update/proxy_lib.sh +else + clashoo_detect_proxy() { :; } +fi +detect_proxy() { clashoo_detect_proxy; } + +fetch_text() { + url="$1" + proxy="$(detect_proxy)" + if command -v curl >/dev/null 2>&1; then + curl -fsSL --connect-timeout "$CONNECT_TIMEOUT" --max-time "$REQUEST_TIMEOUT" ${proxy:+--proxy "$proxy"} "$url" + return $? + fi + if command -v wget >/dev/null 2>&1; then + [ -n "$proxy" ] && { http_proxy="$proxy" https_proxy="$proxy" wget -qO- --timeout="$REQUEST_TIMEOUT" "$url"; return $?; } + wget -qO- --timeout="$REQUEST_TIMEOUT" "$url" + return $? + fi + return 127 +} + +download_file() { + url="$1" + out="$2" + proxy="$(detect_proxy)" + if command -v curl >/dev/null 2>&1; then + curl -fL --connect-timeout "$CONNECT_TIMEOUT" --speed-time "$LOW_SPEED_TIME" --speed-limit "$LOW_SPEED_LIMIT" ${proxy:+--proxy "$proxy"} "$url" -o "$out" + return $? + fi + if [ -n "$proxy" ]; then + http_proxy="$proxy" https_proxy="$proxy" wget -qO "$out" --timeout="$REQUEST_TIMEOUT" "$url" + return $? + fi + wget -qO "$out" --timeout="$REQUEST_TIMEOUT" "$url" +} + +download_url() { + url="$1" + case "$url" in + https://github.com/*) + printf '%s%s\n' "$GITHUB_PROXY_PREFIX" "$url" + ;; + *) + printf '%s\n' "$url" + ;; + esac +} + +root_free_kb() { + df -k / 2>/dev/null | awk 'NR==2 {print $4}' +} + +kb_to_mib() { + awk "BEGIN {printf \"%.0f\", (${1:-0}) / 1024}" +} + +ensure_root_space() { + need_kb="$1" + free_kb="$(root_free_kb)" + [ -n "$free_kb" ] || return 0 + [ "$free_kb" -ge "$need_kb" ] && return 0 + finish 1 "根分区空间不足:可用 $(kb_to_mib "$free_kb") MiB,至少需要 $(kb_to_mib "$need_kb") MiB" +} + +log_install_error() { + file="$1" + [ -s "$file" ] || return 0 + tail -8 "$file" 2>/dev/null | while IFS= read -r line; do + [ -n "$line" ] && log "$line" + done +} + +detect_manager() { + if command -v opkg >/dev/null 2>&1; then + echo opkg + return + fi + if command -v apk >/dev/null 2>&1; then + echo apk + return + fi + echo unsupported +} + +installed_package_version() { + pkg="$1" + if command -v opkg >/dev/null 2>&1; then + opkg status "$pkg" 2>/dev/null | awk -F': ' '/^Version:/{print $2; exit}' + return + fi + if command -v apk >/dev/null 2>&1; then + apk list -I "$pkg" 2>/dev/null | awk '{print $1; exit}' | sed -n "s/^${pkg}-//p" + fi +} + +package_installed() { + pkg="$1" + if command -v opkg >/dev/null 2>&1; then + opkg status "$pkg" 2>/dev/null | awk ' + $1 == "Status:" && $2 == "install" && $NF == "installed" { found = 1 } + END { exit !found } + ' + return + fi + apk info -e "$pkg" >/dev/null 2>&1 +} + +apk_install() { + proxy="$(detect_proxy)" + if [ -n "$proxy" ]; then + http_proxy="$proxy" https_proxy="$proxy" apk add --allow-untrusted --timeout 30 "$@" + else + apk add --allow-untrusted --timeout 30 "$@" + fi +} + +apk_bundle_install() { + printf '%s\n' "$$" >"$APK_UPGRADE_FLAG" + apk_install "$@" + rc=$? + rm -f "$APK_UPGRADE_FLAG" + return "$rc" +} + +verify_downloaded_package() { + file="$1" + [ -s "$file" ] || return 1 + if [ "$PM" = "apk" ]; then + apk verify --allow-untrusted "$file" >/dev/null 2>&1 + return + fi + gzip -t "$file" >/dev/null 2>&1 +} + +download_package_file() { + url="$1" + out="$2" + name="$3" + download_file "$(download_url "$url")" "$out" || finish 1 "下载 ${name} 失败" + verify_downloaded_package "$out" || finish 1 "${name} 文件校验失败" +} + +detect_arch() { + pm="$1" + if [ "$pm" = "opkg" ]; then + opkg print-architecture 2>/dev/null | awk '/^arch / {print $2}' | tail -n 1 + return + fi + # apk: DISTRIB_ARCH is authoritative (e.g. aarch64_cortex-a53); apk --print-arch + # only gives the generic "aarch64" which won't match the .apk package suffix. + if [ -r /etc/openwrt_release ]; then + a="$(sed -n "s/^DISTRIB_ARCH=['\"]\\([^'\"]*\\)['\"]$/\\1/p" /etc/openwrt_release | head -n 1)" + [ -n "$a" ] && { printf '%s\n' "$a"; return; } + fi + apk --print-arch 2>/dev/null +} + +detect_sdk() { + [ -r /etc/openwrt_release ] || return 1 + release="$(sed -n "s/^DISTRIB_RELEASE=['\"]\\([^'\"]*\\)['\"]$/\\1/p" /etc/openwrt_release | head -n 1)" + [ -n "$release" ] || return 1 + printf '%s\n' "$release" | grep -Eo '[0-9]+\.[0-9]+' | head -n 1 +} + +append_unique_word() { + value="$1" + list="$2" + [ -n "$value" ] || { + printf '%s\n' "$list" + return + } + case " $list " in + *" $value "*) ;; + *) list="${list}${list:+ }${value}" ;; + esac + printf '%s\n' "$list" +} + +build_sdk_candidates() { + pm="$1" + candidates="" + detected_sdk="$(detect_sdk || true)" + candidates="$(append_unique_word "$detected_sdk" "$candidates")" + + if [ "$pm" = "opkg" ]; then + for sdk in 24.10 23.05 22.03 21.02; do + candidates="$(append_unique_word "$sdk" "$candidates")" + done + else + for sdk in 25.12 24.10; do + candidates="$(append_unique_word "$sdk" "$candidates")" + done + fi + + printf '%s\n' "$candidates" +} + +find_manifest_value() { + key="$1" + manifest_text="$2" + printf '%s\n' "$manifest_text" | sed -n "s/^${key}=//p" | head -n 1 +} + +load_manifest_urls() { + sdk="$1" + arch="$2" + manifest_url="${FEED_BASE_URL}/${sdk}/${arch}/manifest-clashoo.txt" + manifest_text="$(fetch_text "$manifest_url" || true)" + [ -n "$manifest_text" ] || return 1 + + core_file="$(find_manifest_value "core" "$manifest_text")" + luci_file="$(find_manifest_value "luci" "$manifest_text")" + i18n_file="$(find_manifest_value "i18n" "$manifest_text")" + [ -n "$core_file" ] || return 1 + [ -n "$luci_file" ] || return 1 + case "$core_file" in *."$EXT") ;; *) return 1 ;; esac + case "$luci_file" in *."$EXT") ;; *) return 1 ;; esac + case "$i18n_file" in ""|*."$EXT") ;; *) return 1 ;; esac + + CORE_URL="${FEED_BASE_URL}/${sdk}/${arch}/${core_file}" + LUCI_URL="${FEED_BASE_URL}/${sdk}/${arch}/${luci_file}" + I18N_URL="" + [ -n "$i18n_file" ] && I18N_URL="${FEED_BASE_URL}/${sdk}/${arch}/${i18n_file}" + SOURCE_LABEL="R2 feed ${sdk}/${arch}" + return 0 +} + +load_github_urls() { + arch="$1" + ext="$2" + payload="$(fetch_text "$GITHUB_API_URL" || true)" + [ -n "$payload" ] || return 1 + + urls="$(printf '%s\n' "$payload" | sed -n 's/.*"browser_download_url":[[:space:]]*"\([^"]*\)".*/\1/p')" + [ -n "$urls" ] || return 1 + + if [ "$ext" = "apk" ]; then + CORE_URL="$(printf '%s\n' "$urls" | grep -E '/clashoo-[^-]+.*-r[0-9]+-'"$arch"'\.apk$' | head -n 1)" + LUCI_URL="$(printf '%s\n' "$urls" | grep -E '/luci-app-clashoo-[^-]+.*-r[0-9]+-('"$arch"'|all)\.apk$' | head -n 1)" + I18N_URL="$(printf '%s\n' "$urls" | grep -E '/luci-i18n-clashoo-zh-cn-[^-]+.*-r[0-9]+-('"$arch"'|all)\.apk$' | head -n 1)" + else + CORE_URL="$(printf '%s\n' "$urls" | grep -E '/clashoo_.*_'"$arch"'\.ipk$' | head -n 1)" + LUCI_URL="$(printf '%s\n' "$urls" | grep -E '/luci-app-clashoo_.*_all\.ipk$' | head -n 1)" + I18N_URL="$(printf '%s\n' "$urls" | grep -E '/luci-i18n-clashoo-zh-cn_.*_all\.ipk$' | head -n 1)" + fi + + [ -n "$CORE_URL" ] || return 1 + [ -n "$LUCI_URL" ] || return 1 + SOURCE_LABEL="GitHub latest" + return 0 +} + +# `~` 是版本里的预发布分隔符,而 release 资产文件名会把它换成 `.` +# (clashoo_2026.10.25.6780eee-r3_x86_64.ipk 对应已安装的 2026.10.25~6780eee-r3)。 +# 只比分隔符就会误判成「校验失败」,所以比较前统一归一化,解析时保持文件名原样。 +version_equal() { + [ -n "$1" ] && [ "$(printf '%s' "$1" | tr '~' '.')" = "$(printf '%s' "$2" | tr '~' '.')" ] +} + +package_version_from_url() { + file="${1##*/}" + case "$file" in + clashoo_*_"$ARCH".ipk) + v="${file#clashoo_}" + printf '%s\n' "${v%_${ARCH}.ipk}" + ;; + # 架构名本身可能带下划线(aarch64_generic / arm_cortex-a7_neon-vfpv4), + # 只截到版本号为止,否则尾巴会混进版本串。 + clashoo_*.ipk) printf '%s\n' "$file" | sed -n 's/^clashoo_\([^_]*\)_.*\.ipk$/\1/p' ;; + luci-app-clashoo_*.ipk) printf '%s\n' "$file" | sed -n 's/^luci-app-clashoo_\(.*\)_all\.ipk$/\1/p' ;; + luci-i18n-clashoo-zh-cn_*.ipk) printf '%s\n' "$file" | sed -n 's/^luci-i18n-clashoo-zh-cn_\(.*\)_all\.ipk$/\1/p' ;; + clashoo-*.apk) + printf '%s\n' "$file" | sed -n -e 's/^clashoo-\(.*-r[0-9][0-9]*\)-.*\.apk$/\1/p' -e 's/^clashoo-\(.*-r[0-9][0-9]*\)\.apk$/\1/p' | head -n 1 + ;; + luci-app-clashoo-*.apk) printf '%s\n' "$file" | sed -n -e 's/^luci-app-clashoo-\(.*-r[0-9][0-9]*\)-.*\.apk$/\1/p' -e 's/^luci-app-clashoo-\(.*-r[0-9][0-9]*\)\.apk$/\1/p' | head -n 1 ;; + luci-i18n-clashoo-zh-cn-*.apk) printf '%s\n' "$file" | sed -n -e 's/^luci-i18n-clashoo-zh-cn-\(.*-r[0-9][0-9]*\)-.*\.apk$/\1/p' -e 's/^luci-i18n-clashoo-zh-cn-\(.*-r[0-9][0-9]*\)\.apk$/\1/p' | head -n 1 ;; + *) printf '%s\n' "$file" ;; + esac +} + +resolve_bundle_urls() { + PM="$(detect_manager)" + [ "$PM" != "unsupported" ] || return 1 + ARCH="$(detect_arch "$PM")" + [ -n "$ARCH" ] || return 1 + EXT="ipk" + [ "$PM" = "apk" ] && EXT="apk" + + CORE_URL="" + LUCI_URL="" + I18N_URL="" + SOURCE_LABEL="" + + SDK_CANDIDATES="$(build_sdk_candidates "$PM")" + for sdk in $SDK_CANDIDATES; do + if load_manifest_urls "$sdk" "$ARCH"; then + return 0 + fi + done + + load_github_urls "$ARCH" "$EXT" +} + +backup_config() { + ts="$(date '+%Y%m%d-%H%M%S')" + BACKUP_DIR="/etc/clashoo/backup/component-upgrade-${ts}" + mkdir -p "$BACKUP_DIR/etc-config" "$BACKUP_DIR/etc-clashoo" >/dev/null 2>&1 || return 1 + [ -r /etc/config/clashoo ] && cp -a /etc/config/clashoo "$BACKUP_DIR/etc-config/clashoo" + # 只备份用户配置;Model.bin 等可重新下载的大文件不入备份,避免每次更新 + # 复制 ~5MB 的同一模型,把备份目录撑大、堆满本就紧张的根分区。 + for path in \ + /etc/clashoo/config.yaml \ + /etc/clashoo/config.json \ + /etc/clashoo/*.yaml \ + /etc/clashoo/*.yml \ + /etc/clashoo/*.json + do + [ -e "$path" ] || continue + cp -a "$path" "$BACKUP_DIR/etc-clashoo/" 2>/dev/null || true + done + log "已备份配置:${BACKUP_DIR}" + + # 只保留最近 1 个备份,删除更早的,避免无限堆积占满磁盘。 + ls -dt /etc/clashoo/backup/component-upgrade-* 2>/dev/null | tail -n +2 | while read -r _old; do + [ -n "$_old" ] && rm -rf "$_old" 2>/dev/null + done +} + +restore_config_backup() { + [ -n "${BACKUP_DIR:-}" ] || return 0 + [ -r "$BACKUP_DIR/etc-config/clashoo" ] && cp -a "$BACKUP_DIR/etc-config/clashoo" /etc/config/clashoo + if [ -d "$BACKUP_DIR/etc-clashoo" ]; then + mkdir -p /etc/clashoo + cp -a "$BACKUP_DIR/etc-clashoo/." /etc/clashoo/ 2>/dev/null || true + fi + uci commit clashoo >/dev/null 2>&1 || true +} + +clashoo_was_running() { + /etc/init.d/clashoo status >/dev/null 2>&1 && return 0 + return 1 +} + +restart_web_stack() { + # reload (not restart) rpcd so the LuCI login session survives a plugin + # update — rpcd restart drops every ubus session and forces a re-login + # (issue #12). reload still picks up the new ucode RPC backend. uhttpd needs + # nothing: updated static assets are served on the next request. + /etc/init.d/rpcd reload >/dev/null 2>&1 || true +} + +# which: clashoo(仅核心)/ luci(luci-app + 语言包)。两者拆开,clashoo +# 核心更新频率高于 LuCI,分别更新便于定位失败。 +run_pkg_update() { + which="$1" + log "正在检查组件包" + if ! resolve_bundle_urls; then + finish 1 "未找到适配当前设备的组件包" + fi + log "更新来源:${SOURCE_LABEL}" + + core_ver="$(package_version_from_url "$CORE_URL")" + luci_ver="$(package_version_from_url "$LUCI_URL")" + i18n_ver="" + [ -z "$I18N_URL" ] || i18n_ver="$(package_version_from_url "$I18N_URL")" + core_ver_before="$(installed_package_version clashoo)" + + apk_core_ready() { + package_installed clashoo && version_equal "$(installed_package_version clashoo)" "$core_ver" \ + && [ -x /etc/init.d/clashoo ] && [ -x /usr/share/clashoo/update/component_update.sh ] + } + + apk_luci_ready() { + package_installed luci-app-clashoo && version_equal "$(installed_package_version luci-app-clashoo)" "$luci_ver" \ + && { [ -z "$I18N_URL" ] || { package_installed luci-i18n-clashoo-zh-cn \ + && version_equal "$(installed_package_version luci-i18n-clashoo-zh-cn)" "$i18n_ver"; }; } \ + && [ -r /usr/share/luci/menu.d/luci-app-clashoo.json ] \ + && [ -r /usr/share/rpcd/ucode/luci.clashoo ] + } + + apk_bundle_ready() { + apk_core_ready && apk_luci_ready + } + + apk_update_ready() { + if [ "$which" = "clashoo" ]; then + apk_bundle_ready + else + package_installed clashoo && apk_luci_ready + fi + } + + if [ "$which" = "clashoo" ]; then + log "目标版本:Clashoo 核心 ${core_ver}" + cur_ver="$(installed_package_version clashoo)" + if [ "$PM" = "apk" ] && apk_update_ready; then + finish 0 "Clashoo 核心已是最新版本" + elif [ "$PM" != "apk" ] && version_equal "$cur_ver" "$core_ver"; then + finish 0 "Clashoo 核心已是最新版本" + fi + else + log "目标版本:客户端 ${luci_ver}" + cur_ver="$(installed_package_version luci-app-clashoo)" + if [ "$PM" = "apk" ] && apk_update_ready; then + finish 0 "客户端已是最新版本" + elif [ "$PM" != "apk" ] && version_equal "$cur_ver" "$luci_ver"; then + finish 0 "客户端已是最新版本" + fi + fi + + rm -rf "$TMP_DIR" + mkdir -p "$TMP_DIR" || finish 1 "创建临时目录失败" + + was_running=0 + clashoo_was_running && was_running=1 + backup_config || finish 1 "备份配置失败" + apk_core_file="" + + if [ "$PM" = "apk" ]; then + log "正在下载组件包" + if [ "$which" = "clashoo" ] || ! package_installed clashoo; then + ensure_root_space 98304 + download_package_file "$CORE_URL" "$TMP_DIR/core.${EXT}" "clashoo" + apk_core_file="$TMP_DIR/core.${EXT}" + fi + download_package_file "$LUCI_URL" "$TMP_DIR/luci.${EXT}" "luci-app-clashoo" + if [ -n "$I18N_URL" ]; then + download_package_file "$I18N_URL" "$TMP_DIR/i18n.${EXT}" "语言包" + fi + elif [ "$which" = "clashoo" ]; then + log "正在下载 Clashoo 核心" + download_package_file "$CORE_URL" "$TMP_DIR/core.${EXT}" "clashoo" + else + log "正在下载客户端(LuCI + 语言包)" + download_package_file "$LUCI_URL" "$TMP_DIR/luci.${EXT}" "luci-app-clashoo" + if [ -n "$I18N_URL" ]; then + download_package_file "$I18N_URL" "$TMP_DIR/i18n.${EXT}" "语言包" + fi + fi + + if [ "$PM" != "apk" ] && [ "$which" = "clashoo" ]; then + ensure_root_space 98304 + fi + + if [ "$which" = "clashoo" ]; then + log "正在安装 Clashoo 核心" + if [ "$PM" = "opkg" ]; then + opkg install --force-downgrade "$TMP_DIR/core.${EXT}" >"$TMP_DIR/install.log" 2>&1; rc=$? + else + apk_bundle_install ${apk_core_file:+"$apk_core_file"} "$TMP_DIR/luci.${EXT}" ${I18N_URL:+"$TMP_DIR/i18n.${EXT}"} >"$TMP_DIR/install.log" 2>&1; rc=$? + fi + else + log "正在安装客户端" + if [ "$PM" = "opkg" ]; then + opkg install --force-downgrade "$TMP_DIR/luci.${EXT}" ${I18N_URL:+"$TMP_DIR/i18n.${EXT}"} >"$TMP_DIR/install.log" 2>&1; rc=$? + else + apk_bundle_install ${apk_core_file:+"$apk_core_file"} "$TMP_DIR/luci.${EXT}" ${I18N_URL:+"$TMP_DIR/i18n.${EXT}"} >"$TMP_DIR/install.log" 2>&1; rc=$? + fi + fi + + if [ "$rc" -ne 0 ]; then + if [ "$PM" = "apk" ] && apk_update_ready; then + log "apk 报告了其它软件包的错误,本次组件已升级到目标版本" + rc=0 + else + log_install_error "$TMP_DIR/install.log" + log "安装失败,正在恢复配置备份" + restore_config_backup + finish "$rc" "组件更新失败,已停止操作,请查看组件更新日志" + fi + fi + + validation_failed() { + log "安装校验失败,正在恢复配置备份" + restore_config_backup + finish 1 "组件安装校验失败,已停止操作,请查看组件更新日志" + } + + if [ "$PM" = "apk" ]; then + if ! apk_update_ready; then + log "安装校验失败,正在安全重装组件包" + if [ -z "$apk_core_file" ] && ! package_installed clashoo; then + ensure_root_space 98304 + download_package_file "$CORE_URL" "$TMP_DIR/core.${EXT}" "clashoo" + apk_core_file="$TMP_DIR/core.${EXT}" + fi + apk_bundle_install --force-reinstall ${apk_core_file:+"$apk_core_file"} "$TMP_DIR/luci.${EXT}" \ + ${I18N_URL:+"$TMP_DIR/i18n.${EXT}"} >>"$TMP_DIR/install.log" 2>&1; repair_rc=$? + if [ "$repair_rc" -ne 0 ]; then + log_install_error "$TMP_DIR/install.log" + validation_failed + fi + apk_update_ready || validation_failed + fi + elif [ "$which" = "clashoo" ]; then + package_installed clashoo && version_equal "$(installed_package_version clashoo)" "$core_ver" \ + && [ -x /etc/init.d/clashoo ] && [ -x /usr/share/clashoo/update/component_update.sh ] \ + || validation_failed + else + package_installed luci-app-clashoo && version_equal "$(installed_package_version luci-app-clashoo)" "$luci_ver" \ + && [ -r /usr/share/luci/menu.d/luci-app-clashoo.json ] \ + && [ -r /usr/share/rpcd/ucode/luci.clashoo ] \ + || validation_failed + [ -z "$I18N_URL" ] || package_installed luci-i18n-clashoo-zh-cn || validation_failed + fi + + # APK component updates install the LuCI bundle in the same transaction. + if [ "$which" = "luci" ] || [ "$PM" = "apk" ]; then + log "正在刷新 LuCI 服务" + restart_web_stack + fi + core_ver_after="$(installed_package_version clashoo)" + restart_clashoo=0 + [ "$which" = "clashoo" ] && restart_clashoo=1 + [ "$PM" = "apk" ] && [ "$core_ver_before" != "$core_ver_after" ] && restart_clashoo=1 + if [ "$restart_clashoo" -eq 1 ] && [ "$was_running" -eq 1 ] && [ "$(uci -q get clashoo.config.enable 2>/dev/null)" = "1" ]; then + log "Clashoo 原本运行中,正在重启服务" + sh /usr/share/clashoo/rpc/rpc_async.sh restart >/dev/null 2>&1 || /etc/init.d/clashoo restart >/dev/null 2>&1 || true + elif [ "$restart_clashoo" -eq 1 ]; then + log "Clashoo 原本未运行,保持停止状态" + fi + + finish 0 "组件更新完成" +} + +run_core_update() { + dcore="$1" + label="$2" + log "正在更新 ${label}" + # Pass the target as an arg so core_download refreshes only that binary + # without switching the active kernel. It restarts only if the target is the + # currently-running core (see core_download.sh finalize). Switching kernels + # stays a separate, explicit user action. + touch /var/run/core_update + sh /usr/share/clashoo/update/core_download.sh "$dcore" >>/tmp/clash_update.txt 2>&1 + rc=$? + rm -f /var/run/core_update + [ "$rc" -eq 0 ] || finish "$rc" "${label} 更新失败" + finish 0 "${label} 更新完成" +} + +run_lgbm_update() { + log "正在更新 Smart LightGBM 模型" + sh /usr/share/clashoo/update/lgbm_update.sh >/tmp/lgbm_update.log 2>&1 + rc=$? + tail -5 /tmp/lgbm_update.log 2>/dev/null | while IFS= read -r line; do + [ -n "$line" ] && log "$line" + done + [ "$rc" -eq 0 ] || finish "$rc" "LightGBM 模型更新失败" + finish 0 "LightGBM 模型更新完成" +} + +run_china_update() { + log "正在更新大陆白名单" + sh /usr/share/clashoo/update/update_china_ip.sh >>/tmp/clash_update.txt 2>&1 + rc=$? + tail -6 /tmp/clash_update.txt 2>/dev/null | while IFS= read -r line; do + [ -n "$line" ] && printf '%s\n' "$line" | grep -q '白名单' && log "$line" + done + [ "$rc" -eq 0 ] || finish "$rc" "大陆白名单更新失败" + finish 0 "大陆白名单更新完成" +} + +run_geoip_update() { + log "正在更新 GeoIP / GeoSite" + touch /var/run/geoip_update + sh /usr/share/clashoo/update/geoip.sh >/tmp/geoip_update.txt 2>&1 + rc=$? + rm -f /var/run/geoip_update + tail -8 /tmp/geoip_update.txt 2>/dev/null | while IFS= read -r line; do + [ -n "$line" ] && log "$line" + done + [ "$rc" -eq 0 ] || finish "$rc" "GeoIP / GeoSite 更新失败" + finish 0 "GeoIP / GeoSite 更新完成" +} + +COMPONENT="${1:-}" +VARIANT="${2:-}" # mihomo/singbox: stable | alpha +[ -n "$COMPONENT" ] || { + echo "usage: $0 [stable|alpha]" + exit 2 +} + +mkdir -p "$(dirname "$RUN_FILE")" >/dev/null 2>&1 +printf '%s\n' "$COMPONENT" >"$RUN_FILE" +STATUS="running" +log "组件更新任务启动:${COMPONENT}${VARIANT:+ ($VARIANT)}" + +case "$COMPONENT" in + clashoo) run_pkg_update clashoo ;; + luci) run_pkg_update luci ;; + mihomo) + if [ "$VARIANT" = "alpha" ]; then run_core_update 3 "mihomo Alpha 版" + else run_core_update 2 "mihomo 稳定版"; fi ;; + singbox) + if [ "$VARIANT" = "alpha" ]; then run_core_update 5 "sing-box Alpha 版" + else run_core_update 4 "sing-box 稳定版"; fi ;; + smart) run_core_update 1 "mihomo Smart 版" ;; + lgbm) run_lgbm_update ;; + china) run_china_update ;; + geoip) run_geoip_update ;; + *) finish 2 "未知组件:${COMPONENT}" ;; +esac diff --git a/clashoo/files/usr/share/clashoo/update/core_download.sh b/clashoo/files/usr/share/clashoo/update/core_download.sh new file mode 100755 index 00000000..9407fd1a --- /dev/null +++ b/clashoo/files/usr/share/clashoo/update/core_download.sh @@ -0,0 +1,1233 @@ +#!/bin/sh + +LOG_FILE="/tmp/clash_update.txt" +MODELTYPE=$(uci get clashoo.config.download_core 2>/dev/null) +# When invoked with a target dcore arg (the component "core update" button), +# download that core WITHOUT switching the active kernel — see finalize(). +# No arg = legacy callers (manual download_core RPC / boot) use the active dcore. +TARGET_DCORE="$1" +CORETYPE=${TARGET_DCORE:-$(uci get clashoo.config.dcore 2>/dev/null)} +MIRROR_PREFIX=$(uci get clashoo.config.core_mirror_prefix 2>/dev/null) +CUSTOM_CORE_URL=$(uci get clashoo.config.core_download_url 2>/dev/null) +SINGBOX_STABLE_TARGET="${CLASHOO_SINGBOX_STABLE_TARGET:-/usr/share/clashoo/bin/sing-box-stable}" +SINGBOX_ALPHA_TARGET="${CLASHOO_SINGBOX_ALPHA_TARGET:-/usr/share/clashoo/bin/sing-box-alpha}" +SINGBOX_TMP_TEMPLATE="${CLASHOO_SINGBOX_TMP_TEMPLATE:-/tmp/clashoo-singbox.XXXXXX}" +CORE_DOWNLOAD_LOCK_DIR="${CLASHOO_CORE_DOWNLOAD_LOCK_DIR:-/var/lock/clashoo-core-download.lock}" +CORE_DOWNLOAD_LOCK_HELD=0 +CORE_INSTALLED=0 +CONNECT_TIMEOUT=15 +REQUEST_TIMEOUT=30 +DOWNLOAD_TIMEOUT=60 +LOW_SPEED_TIME=15 +LOW_SPEED_LIMIT=32768 +ATTEMPTS_PER_MIRROR=1 +MAX_TOTAL_SECONDS=200 +TAG_FETCH_RETRIES=2 +START_TS=$(date +%s) + +write_log() { + echo " $(date "+%Y-%m-%d %H:%M:%S") - $1" >> "$LOG_FILE" +} + +core_download_pid_alive() { + local pid + pid="$1" + [ -d "/proc/$pid" ] && return 0 + kill -0 "$pid" >/dev/null 2>&1 +} + +acquire_core_download_lock() { + local owner parent + parent="$(dirname "$CORE_DOWNLOAD_LOCK_DIR")" + mkdir -p "$parent" >/dev/null 2>&1 || return 1 + while :; do + if mkdir "$CORE_DOWNLOAD_LOCK_DIR" 2>/dev/null; then + printf '%s\n' "$$" > "$CORE_DOWNLOAD_LOCK_DIR/pid" || { + rmdir "$CORE_DOWNLOAD_LOCK_DIR" >/dev/null 2>&1 || true + return 1 + } + CORE_DOWNLOAD_LOCK_HELD=1 + return 0 + fi + + if [ ! -e "$CORE_DOWNLOAD_LOCK_DIR/pid" ]; then + # A fresh owner creates the directory before writing its PID. Only + # remove an actually empty directory; otherwise treat it as busy. + if rmdir "$CORE_DOWNLOAD_LOCK_DIR" >/dev/null 2>&1; then + continue + fi + return 1 + fi + owner="$(sed -n '1p' "$CORE_DOWNLOAD_LOCK_DIR/pid" 2>/dev/null)" + case "$owner" in + '' ) return 1 ;; + *[!0-9]*) + rm -f "$CORE_DOWNLOAD_LOCK_DIR/pid" >/dev/null 2>&1 || return 1 + rmdir "$CORE_DOWNLOAD_LOCK_DIR" >/dev/null 2>&1 || return 1 + ;; + *) + if core_download_pid_alive "$owner"; then + return 1 + fi + rm -f "$CORE_DOWNLOAD_LOCK_DIR/pid" >/dev/null 2>&1 || return 1 + rmdir "$CORE_DOWNLOAD_LOCK_DIR" >/dev/null 2>&1 || return 1 + ;; + esac + done +} + +release_core_download_lock() { + local owner + [ "$CORE_DOWNLOAD_LOCK_HELD" = "1" ] || return 0 + owner="$(sed -n '1p' "$CORE_DOWNLOAD_LOCK_DIR/pid" 2>/dev/null)" + if [ "$owner" = "$$" ]; then + rm -f "$CORE_DOWNLOAD_LOCK_DIR/pid" >/dev/null 2>&1 + rmdir "$CORE_DOWNLOAD_LOCK_DIR" >/dev/null 2>&1 || true + fi + CORE_DOWNLOAD_LOCK_HELD=0 +} + +finalize() { + if [ "$CORE_INSTALLED" = "1" ]; then + if [ -n "$TARGET_DCORE" ]; then + # "core update" path: only refresh the binary. Apply (restart) only + # when we updated the kernel that is actually running, so updating + # smart / sing-box never hijacks the active kernel — and updating + # several cores doesn't rotate through a restart for each. Switching + # kernels stays a separate user action (set_core handles strategy). + if [ "$TARGET_DCORE" = "$(uci get clashoo.config.dcore 2>/dev/null)" ]; then + write_log "当前内核已更新,重启 Clashoo 生效" + if [ "$(uci -q get clashoo.config.enable 2>/dev/null)" = "1" ]; then + /etc/init.d/clashoo restart >/dev/null 2>&1 || write_log "重启 Clashoo 失败" + fi + else + write_log "内核二进制已更新(非当前内核,不切换、不重启)" + fi + else + # legacy callers (manual download_core RPC / boot): unchanged behavior + if [ "$CORETYPE" = "1" ]; then + uci set clashoo.config.core_type="mihomo" 2>/dev/null + uci set clashoo.config.smart_auto_switch="1" 2>/dev/null + uci commit clashoo 2>/dev/null + write_log "Smart 内核已就绪,自动启用 Smart 策略" + fi + write_log "内核已替换,重启 Clashoo" + if [ "$(uci -q get clashoo.config.enable 2>/dev/null)" = "1" ]; then + /etc/init.d/clashoo restart >/dev/null 2>&1 || write_log "重启 Clashoo 失败" + fi + fi + fi + rm -f /var/run/core_update >/dev/null 2>&1 + release_core_download_lock +} + +timed_out() { + now_ts=$(date +%s) + [ $((now_ts - START_TS)) -ge "$MAX_TOTAL_SECONDS" ] +} + +ensure_not_timed_out() { + if timed_out; then + write_log "下载超时(超过 ${MAX_TOTAL_SECONDS} 秒)" + return 1 + fi + return 0 +} + +normalize_prefix() { + p="$1" + [ -z "$p" ] && return + case "$p" in + */) printf '%s\n' "$p" ;; + *) printf '%s/\n' "$p" ;; + esac +} + +mirror_prefixes() { + # 默认源按 2026-04 实测排序:mirror.ghproxy.com 已挂、hub.gitmirror.com/git.886.be 全死、 + # gh.idayer.com/ghproxy.net 限速截断;只保留 gh-proxy.com + ghfast.top 两条快通道。 + # 末尾会自动追加裸 GitHub(download_with_mirrors 末尾会 push 一个空 prefix)。 + custom="$(normalize_prefix "$MIRROR_PREFIX")" + prefixes="" + for prefix in "$custom" "https://gh-proxy.com/" "https://ghfast.top/"; do + [ -n "$prefix" ] || continue + case " $prefixes " in + *" $prefix "*) ;; + *) prefixes="${prefixes}${prefixes:+ }${prefix}" ;; + esac + done + printf '%s\n' "$prefixes" +} + +prefixed_url() { + prefix="$1" + base_url="$2" + if [ -z "$prefix" ]; then + echo "$base_url" + else + echo "${prefix}${base_url}" + fi +} + +fetch_url_try() { + url="$1" + if command -v curl >/dev/null 2>&1; then + curl -fsSL --connect-timeout "$CONNECT_TIMEOUT" --max-time "$REQUEST_TIMEOUT" -A "Clash/OpenWRT" "$url" + return $? + fi + if command -v wget >/dev/null 2>&1; then + wget -qO- --timeout="$CONNECT_TIMEOUT" --user-agent="Clash/OpenWRT" "$url" + return $? + fi + return 127 +} + +# Route core downloads through the running core in kernel-only mode (shared +# logic in proxy_lib.sh: handles smart core + sing-box's own profile port). +if [ "${CLASHOO_CORE_DOWNLOAD_LIB_ONLY:-0}" = "1" ]; then + detect_proxy() { :; } +else + . /usr/share/clashoo/update/proxy_lib.sh + detect_proxy() { clashoo_detect_proxy; } +fi + +download_file_try() { + url="$1" + out="$2" + proxy="$3" + if command -v curl >/dev/null 2>&1; then + if [ -n "$proxy" ]; then + curl -fsSL --connect-timeout "$CONNECT_TIMEOUT" --max-time "$DOWNLOAD_TIMEOUT" --speed-time "$LOW_SPEED_TIME" --speed-limit "$LOW_SPEED_LIMIT" --retry 0 -A "Clash/OpenWRT" --proxy "$proxy" "$url" -o "$out" + else + curl -fsSL --connect-timeout "$CONNECT_TIMEOUT" --max-time "$DOWNLOAD_TIMEOUT" --speed-time "$LOW_SPEED_TIME" --speed-limit "$LOW_SPEED_LIMIT" --retry 0 -A "Clash/OpenWRT" "$url" -o "$out" + fi + return $? + fi + if command -v wget >/dev/null 2>&1; then + wget -q --timeout="$REQUEST_TIMEOUT" --user-agent="Clash/OpenWRT" "$url" -O "$out" + return $? + fi + return 127 +} + +head_url_try() { + url="$1" + if command -v curl >/dev/null 2>&1; then + curl -fsSIL --connect-timeout "$CONNECT_TIMEOUT" --max-time "$REQUEST_TIMEOUT" -A "Clash/OpenWRT" "$url" >/dev/null 2>&1 + return $? + fi + wget -q --spider --timeout="$CONNECT_TIMEOUT" --user-agent="Clash/OpenWRT" "$url" +} + +map_mihomo_arch() { + # 上游 amd64 默认 v3 微架构(要 AVX2)跑不起来;default 给 compatible 兜底 + case "$1" in + amd64-compatible) echo "linux-amd64-compatible" ;; + amd64-v1) echo "linux-amd64-v1" ;; + amd64-v2) echo "linux-amd64-v2" ;; + amd64-v3) echo "linux-amd64-v3" ;; + amd64|x86_64) echo "linux-amd64-compatible" ;; + arm64|aarch64*) echo "linux-arm64" ;; + armv7|arm_cortex-a[7-9]*|arm_cortex-a1[0-9]*) echo "linux-armv7" ;; + armv6|arm_cortex-a[56]*|arm_arm1176*) echo "linux-armv6" ;; + armv5|arm_cortex-a5*|arm_arm926*) echo "linux-armv5" ;; + 386|i[3-6]86|i386*) echo "linux-386" ;; + mipsle|mipsel_24kc) echo "linux-mipsle-softfloat" ;; + mips|mips_24kc) echo "linux-mips-softfloat" ;; + mips64le|mips64el_mips64r2) echo "linux-mips64le" ;; + mips64|mips64_mips64r2) echo "linux-mips64" ;; + riscv64) echo "linux-riscv64" ;; + *) echo "linux-amd64-compatible" ;; + esac +} + +map_singbox_arch() { + case "$1" in + amd64|amd64-compatible|amd64-v1|amd64-v2|amd64-v3) echo "amd64" ;; + arm64|aarch64*) echo "arm64" ;; + armv7|arm_cortex-a[7-9]*|arm_cortex-a1[0-9]*) echo "armv7" ;; + armv6|arm_cortex-a[56]*|arm_arm1176*) echo "armv6" ;; + armv5|arm_cortex-a5*|arm_arm926*) echo "armv5" ;; + 386|i[3-6]86|i386*) echo "386" ;; + mips|mips_24kc) echo "mips" ;; + mipsle|mipsel_24kc) echo "mipsle" ;; + mips64|mips64_mips64r2) echo "mips64" ;; + mips64le|mips64el_mips64r2) echo "mips64le" ;; + riscv64) echo "riscv64" ;; + *) echo "amd64" ;; + esac +} + +map_openwrt_arch() { + case "$1" in + amd64|amd64-compatible|amd64-v1|amd64-v2|amd64-v3|x86_64) echo "x86_64" ;; + arm64) echo "aarch64_generic" ;; + armv7) echo "arm_cortex-a7_neon-vfpv4" ;; + armv6) echo "arm_arm1176jzf-s_vfp" ;; + armv5) echo "arm_arm926ej-s" ;; + 386) echo "i386_pentium4" ;; + mipsle) echo "mipsel_24kc" ;; + mips) echo "mips_24kc" ;; + mips64le) echo "mips64el_mips64r2" ;; + mips64) echo "mips64_mips64r2" ;; + *) echo "" ;; + esac +} + +# Auto-detect normalized arch (MODELTYPE format) from the running device. +# Tries opkg → DISTRIB_ARCH → apk --print-arch → uname -m. +# Normalizes to MODELTYPE format (same vocabulary as luci.clashoo and system.js). +_auto_detect_modeltype() { + local raw norm + if command -v opkg >/dev/null 2>&1; then + raw="$(opkg print-architecture 2>/dev/null | awk '$1=="arch" && $2!="all" && $2!="noarch" {a=$2} END{print a}')" + fi + if [ -z "$raw" ] && [ -r /etc/openwrt_release ]; then + raw="$(sed -n "s/^DISTRIB_ARCH=['\"]\\([^'\"]*\\)['\"]$/\\1/p" /etc/openwrt_release | head -n 1)" + fi + if [ -z "$raw" ] && command -v apk >/dev/null 2>&1; then + raw="$(apk --print-arch 2>/dev/null)" + fi + [ -z "$raw" ] && raw="$(uname -m 2>/dev/null)" + [ -z "$raw" ] && return 1 + case "$raw" in + x86_64|amd64) norm="amd64-compatible" ;; + aarch64*|arm64) norm="arm64" ;; + armv7*|arm_cortex-a[7-9]*|arm_cortex-a1[0-9]*) norm="armv7" ;; + armv6*|arm_cortex-a[56]*|arm_arm1176*) norm="armv6" ;; + armv5*|arm_cortex-a5*|arm_arm926*) norm="armv5" ;; + arm*) norm="armv5" ;; + i[3-6]86|i386*) norm="386" ;; + mips64el*) norm="mips64le" ;; + mips64*) norm="mips64" ;; + mipsel*) norm="mipsle" ;; + mips*) norm="mips" ;; + riscv64*) norm="riscv64" ;; + esac + [ -z "$norm" ] && return 1 + echo "$norm" + return 0 +} + +_is_amd64_arch() { + case "$1" in + amd64-compatible|amd64-v1|amd64-v2|amd64-v3) return 0 ;; + *) return 1 ;; + esac +} + +append_unique_line() { + list="$1" + line="$2" + [ -z "$line" ] && { + printf '%s' "$list" + return + } + case " +$list +" in + *" +$line +"*) + printf '%s' "$list" + ;; + *) + if [ -n "$list" ]; then + printf '%s\n%s' "$list" "$line" + else + printf '%s' "$line" + fi + ;; + esac +} + +pick_highest_stable_tag() { + printf '%s\n' "$1" | sed '/^$/d;s/\r$//' | grep -E '^v[0-9]+([.][0-9]+){1,3}$' | sort -V | tail -n 1 +} + +pick_highest_prerelease_tag() { + printf '%s\n' "$1" | sed '/^$/d;s/\r$//' | grep -E '^v[0-9]+([.][0-9]+){1,3}-[0-9A-Za-z.-]+$' | sort -V | tail -n 1 +} + +extract_release_tags_by_prerelease() { + payload="$1" + is_prerelease="$2" + + if command -v jsonfilter >/dev/null 2>&1; then + printf '%s' "$payload" | jsonfilter -e "@[@.prerelease=${is_prerelease}].tag_name" 2>/dev/null + return + fi + + tags="$(printf '%s' "$payload" | grep -o '"tag_name"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/')" + flags="$(printf '%s' "$payload" | grep -o '"prerelease"[[:space:]]*:[[:space:]]*[a-z]*' | sed 's/.*:[[:space:]]*//')" + [ -z "$tags" ] && return + [ -z "$flags" ] && return + + tags_file="/tmp/clashoo-tags-$$" + flags_file="/tmp/clashoo-pre-$$" + printf '%s\n' "$tags" > "$tags_file" + printf '%s\n' "$flags" > "$flags_file" + awk -v want="$is_prerelease" 'NR==FNR { t[FNR]=$0; next } $0==want { print t[FNR] }' "$tags_file" "$flags_file" + rm -f "$tags_file" "$flags_file" >/dev/null 2>&1 +} + +detect_openwrt_arch() { + local arch + if command -v opkg >/dev/null 2>&1; then + arch="$(opkg print-architecture 2>/dev/null | awk '$1=="arch" && $2!="all" && $2!="noarch" {a=$2} END{print a}')" + [ -n "$arch" ] && { echo "$arch"; return 0; } + fi + # DISTRIB_ARCH is authoritative for OpenWrt package arch naming. + # apk --print-arch may return "aarch64" while sing-box release assets + # use the full sub-arch (e.g. aarch64_generic / aarch64_cortex-a53). + if [ -r /etc/openwrt_release ]; then + arch="$(sed -n "s/^DISTRIB_ARCH=['\"]\\([^'\"]*\\)['\"]$/\\1/p" /etc/openwrt_release | head -n 1)" + [ -n "$arch" ] && { echo "$arch"; return 0; } + fi + if command -v apk >/dev/null 2>&1; then + arch="$(apk --print-arch 2>/dev/null)" + [ -n "$arch" ] && { echo "$arch"; return 0; } + fi + map_openwrt_arch "$MODELTYPE" +} + +singbox_target_path() { + case "$CORETYPE" in + 5) printf '%s\n' "$SINGBOX_ALPHA_TARGET" ;; + *) printf '%s\n' "$SINGBOX_STABLE_TARGET" ;; + esac +} + +extract_tar_file() { + local archive dest + archive="$1" + dest="$2" + case "$archive" in + *.gz) tar -xzf "$archive" -C "$dest" >/dev/null 2>&1 ;; + *.xz) tar -xJf "$archive" -C "$dest" >/dev/null 2>&1 || tar -xf "$archive" -C "$dest" >/dev/null 2>&1 ;; + *.zst) + if command -v unzstd >/dev/null 2>&1; then + unzstd -c "$archive" 2>/dev/null | tar -xf - -C "$dest" >/dev/null 2>&1 + elif command -v zstd >/dev/null 2>&1; then + zstd -dc "$archive" 2>/dev/null | tar -xf - -C "$dest" >/dev/null 2>&1 + else + return 1 + fi + ;; + *) tar -xf "$archive" -C "$dest" >/dev/null 2>&1 || tar -xzf "$archive" -C "$dest" >/dev/null 2>&1 ;; + esac +} + +extract_ar_data_tar() { + local archive dest offset name size member + archive="$1" + dest="$2" + offset=8 + [ "$(dd if="$archive" bs=8 count=1 2>/dev/null)" = '!' ] || return 1 + while :; do + # Read fixed-width ar fields directly. Do not put the full 60-byte + # header in command substitution: shell command substitution strips + # trailing newlines/spaces and makes length checks unreliable in ash. + name="$(dd if="$archive" bs=1 skip="$offset" count=16 2>/dev/null | sed 's/[[:space:]].*$//')" + name="${name%/}" + size="$(dd if="$archive" bs=1 skip=$((offset + 48)) count=10 2>/dev/null | tr -d '[:space:]')" + case "$size" in + ''|*[!0-9]*) return 1 ;; + esac + member="$dest/$name" + case "$name" in + data.tar.*) + dd if="$archive" of="$member" bs=1 skip=$((offset + 60)) count="$size" 2>/dev/null || return 1 + printf '%s\n' "$member" + return 0 + ;; + esac + offset=$((offset + 60 + size)) + [ $((offset % 2)) -eq 0 ] || offset=$((offset + 1)) + done +} + +extract_singbox_payload() { + local pkg rootdir outer workdir payload data_tar candidate + pkg="$1" + rootdir="$2" + outer="${rootdir}.outer" + workdir="$(dirname "$rootdir")" + payload="$workdir/sing-box.payload" + data_tar="" + rm -f "$payload" 2>/dev/null + rm -rf "$rootdir" "$outer" 2>/dev/null + if ! mkdir -p "$rootdir" "$outer"; then + rm -rf "$rootdir" "$outer" 2>/dev/null + return 1 + fi + + # IPK assets are ar/tar containers with a compressed data.tar.* member; + # official fallback archives contain sing-box directly below a version dir. + if tar -tf "$pkg" 2>/dev/null | grep -Eq '(^|/)data\.tar\.(gz|xz|zst)$'; then + extract_tar_file "$pkg" "$outer" || { + rm -rf "$rootdir" "$outer" 2>/dev/null + return 1 + } + for candidate in "$outer"/data.tar.*; do + [ -f "$candidate" ] || continue + data_tar="$candidate" + break + done + if [ -z "$data_tar" ] || ! extract_tar_file "$data_tar" "$rootdir"; then + rm -rf "$rootdir" "$outer" 2>/dev/null + return 1 + fi + else + data_tar="$(extract_ar_data_tar "$pkg" "$outer" 2>/dev/null || true)" + if [ -n "$data_tar" ]; then + if ! extract_tar_file "$data_tar" "$rootdir"; then + rm -rf "$rootdir" "$outer" 2>/dev/null + return 1 + fi + else + if ! extract_tar_file "$pkg" "$rootdir"; then + rm -rf "$rootdir" "$outer" 2>/dev/null + return 1 + fi + fi + fi + + candidate="$(find "$rootdir" -type f -name sing-box 2>/dev/null | head -n 1)" + if [ -n "$candidate" ] && [ -f "$candidate" ]; then + if ! cp -f "$candidate" "$payload"; then + rm -rf "$rootdir" "$outer" 2>/dev/null + return 1 + fi + rm -rf "$rootdir" "$outer" 2>/dev/null + printf '%s\n' "$payload" + return 0 + fi + rm -rf "$rootdir" "$outer" 2>/dev/null + return 1 +} + +cleanup_singbox_extract_workdir() { + local workdir rootdir + workdir="$1" + rootdir="$workdir/root" + rm -rf "$rootdir" "${rootdir}.outer" "$workdir" 2>/dev/null +} + +install_singbox_openwrt_asset() { + local pkg target workdir rootdir bin extract_rc rc + pkg="$1" + target="${2:-$(singbox_target_path)}" + workdir="$(mktemp -d "$SINGBOX_TMP_TEMPLATE" 2>/dev/null)" || return 1 + rootdir="$workdir/root" + bin="$(extract_singbox_payload "$pkg" "$rootdir")" + extract_rc=$? + if [ "$extract_rc" -eq 0 ]; then + install_with_rollback "$bin" "$target" "${VER:-}" + rc=$? + else + rc=1 + fi + cleanup_singbox_extract_workdir "$workdir" + return "$rc" +} + +install_singbox_tar_asset() { + local archive target workdir rootdir bin extract_rc rc + archive="$1" + target="${2:-$(singbox_target_path)}" + workdir="$(mktemp -d "$SINGBOX_TMP_TEMPLATE" 2>/dev/null)" || return 1 + rootdir="$workdir/root" + bin="$(extract_singbox_payload "$archive" "$rootdir")" + extract_rc=$? + if [ "$extract_rc" -eq 0 ]; then + install_with_rollback "$bin" "$target" "${VER:-}" + rc=$? + else + rc=1 + fi + cleanup_singbox_extract_workdir "$workdir" + return "$rc" +} + +fetch_latest_tag() { + repo="$1" + api_url="https://api.github.com/repos/${repo}/releases/latest" + list_api="https://api.github.com/repos/${repo}/releases?per_page=100" + web_url="https://github.com/${repo}/releases/latest" + candidates="" + + # Fast path: prefer official GitHub API first to avoid stale mirror cache. + p="" + ensure_not_timed_out || return 1 + u="$(prefixed_url "$p" "$api_url")" + i=1 + while [ "$i" -le "$TAG_FETCH_RETRIES" ]; do + ensure_not_timed_out || return 1 + tag="$(fetch_url_try "$u" | sed -n 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -n 1)" + case "$tag" in + v*[-]*) + : + ;; + *) + [ -n "$tag" ] && echo "$tag" && return 0 + ;; + esac + sleep 1 + i=$((i + 1)) + done + + for p in $(mirror_prefixes); do + ensure_not_timed_out || return 1 + u="$(prefixed_url "$p" "$api_url")" + i=1 + while [ "$i" -le "$TAG_FETCH_RETRIES" ]; do + ensure_not_timed_out || return 1 + tag="$(fetch_url_try "$u" | sed -n 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -n 1)" + case "$tag" in + v*[-]*) + # stable channel should ignore prerelease-like tags from stale mirrors + : + ;; + *) + candidates="$(append_unique_line "$candidates" "$tag")" + ;; + esac + sleep 1 + i=$((i + 1)) + done + done + + p="" + ensure_not_timed_out || return 1 + u="$(prefixed_url "$p" "$list_api")" + i=1 + while [ "$i" -le "$TAG_FETCH_RETRIES" ]; do + ensure_not_timed_out || return 1 + payload="$({ fetch_url_try "$u" || true; })" + tags="$(extract_release_tags_by_prerelease "$payload" false)" + best="$(pick_highest_stable_tag "$tags")" + [ -n "$best" ] && echo "$best" && return 0 + sleep 1 + i=$((i + 1)) + done + + for p in $(mirror_prefixes); do + ensure_not_timed_out || return 1 + u="$(prefixed_url "$p" "$list_api")" + i=1 + while [ "$i" -le "$TAG_FETCH_RETRIES" ]; do + ensure_not_timed_out || return 1 + payload="$({ fetch_url_try "$u" || true; })" + tags="$(extract_release_tags_by_prerelease "$payload" false)" + for tag in $tags; do + candidates="$(append_unique_line "$candidates" "$tag")" + done + sleep 1 + i=$((i + 1)) + done + done + + for p in $(mirror_prefixes); do + ensure_not_timed_out || return 1 + u="$(prefixed_url "$p" "$web_url")" + i=1 + while [ "$i" -le "$TAG_FETCH_RETRIES" ]; do + ensure_not_timed_out || return 1 + if command -v curl >/dev/null 2>&1; then + tag="$(curl -fsSIL --connect-timeout "$CONNECT_TIMEOUT" --max-time "$REQUEST_TIMEOUT" -A "Clash/OpenWRT" "$u" 2>/dev/null | sed -n 's#^[Ll]ocation: .*/releases/tag/\([^[:space:]\r]*\).*#\1#p' | head -n 1)" + else + tag="$(wget -S --spider --timeout="$CONNECT_TIMEOUT" --user-agent="Clash/OpenWRT" "$u" 2>&1 | sed -n 's#^ Location: .*/releases/tag/\([^[:space:]]*\).*#\1#p' | head -n 1)" + fi + case "$tag" in + v*[-]*) + : + ;; + *) + candidates="$(append_unique_line "$candidates" "$tag")" + ;; + esac + sleep 1 + i=$((i + 1)) + done + done + + releases_url="https://github.com/${repo}/releases" + for p in $(mirror_prefixes); do + ensure_not_timed_out || return 1 + u="$(prefixed_url "$p" "$releases_url")" + tag="$(fetch_url_try "$u" | sed -n 's#.*releases/tag/\(v[0-9][^\"/?]*\).*#\1#p' | head -n 1)" + case "$tag" in + v*[-]*) + : + ;; + *) + candidates="$(append_unique_line "$candidates" "$tag")" + ;; + esac + done + + # 终极兜底:直连 GitHub 爬取 releases 页面(镜像都失效时备用) + ensure_not_timed_out || return 1 + p="" + u="$(prefixed_url "$p" "$releases_url")" + tag="$(fetch_url_try "$u" 2>/dev/null | sed -n 's#.*releases/tag/\(v[0-9][^"/?]*\).*#\1#p' | head -n 1)" + case "$tag" in + v*[-]*) + ;; + *) + [ -n "$tag" ] && { echo "$tag"; return 0; } + ;; + esac + # 再试一次 releases/latest 的 302 跳转 + u="$(prefixed_url "$p" "$web_url")" + if command -v curl >/dev/null 2>&1; then + tag="$(curl -fsSIL --connect-timeout "$CONNECT_TIMEOUT" --max-time "$REQUEST_TIMEOUT" -A "Clash/OpenWRT" "$u" 2>/dev/null | sed -n 's#^[Ll]ocation: .*/releases/tag/\([^[:space:][:cntrl:]]*\).*#\1#p' | head -n 1)" + else + tag="$(wget -S --spider --timeout="$CONNECT_TIMEOUT" --user-agent="Clash/OpenWRT" "$u" 2>&1 | sed -n 's#^ Location: .*/releases/tag/\([^[:space:]]*\).*#\1#p' | head -n 1)" + fi + [ -n "$tag" ] && { echo "$tag"; return 0; } + +best="$(pick_highest_stable_tag "$candidates")" + [ -n "$best" ] && { + echo "$best" + return 0 + } + fallback="$(printf '%s\n' "$candidates" | sed '/^$/d' | head -n 1)" + [ -n "$fallback" ] && { + echo "$fallback" + return 0 + } + + return 1 +} + +fetch_prerelease_tag() { + repo="$1" + api_url="https://api.github.com/repos/${repo}/releases?per_page=100" + candidates="" + + # Fast path: official GitHub API first. + p="" + ensure_not_timed_out || return 1 + u="$(prefixed_url "$p" "$api_url")" + i=1 + while [ "$i" -le "$TAG_FETCH_RETRIES" ]; do + ensure_not_timed_out || return 1 + payload="$({ fetch_url_try "$u" || true; })" + tags="$(extract_release_tags_by_prerelease "$payload" true)" + best="$(pick_highest_prerelease_tag "$tags")" + [ -n "$best" ] && echo "$best" && return 0 + sleep 1 + i=$((i + 1)) + done + + for p in $(mirror_prefixes); do + ensure_not_timed_out || return 1 + u="$(prefixed_url "$p" "$api_url")" + i=1 + while [ "$i" -le "$TAG_FETCH_RETRIES" ]; do + ensure_not_timed_out || return 1 + payload="$({ fetch_url_try "$u" || true; })" + tags="$(extract_release_tags_by_prerelease "$payload" true)" + for tag in $tags; do + candidates="$(append_unique_line "$candidates" "$tag")" + done + sleep 1 + i=$((i + 1)) + done + done + + best="$(pick_highest_prerelease_tag "$candidates")" + [ -n "$best" ] && { + echo "$best" + return 0 + } + fallback="$(printf '%s\n' "$candidates" | sed '/^$/d' | head -n 1)" + [ -n "$fallback" ] && { + echo "$fallback" + return 0 + } + + return 1 +} + +fetch_expanded_asset_names() { + repo="$1" + tag="$2" + url="https://github.com/${repo}/releases/expanded_assets/${tag}" + + for p in $(mirror_prefixes) ""; do + ensure_not_timed_out || return 1 + u="$(prefixed_url "$p" "$url")" + assets="$(fetch_url_try "$u" | sed -n "s#.*releases/download/${tag}/\\([^\\\"?]*\\)\\\".*#\\1#p")" + [ -n "$assets" ] && printf '%s\n' "$assets" && return 0 + done + + return 1 +} + +pick_reachable_release_url() { + base_url="$1" + for p in $(mirror_prefixes) ""; do + ensure_not_timed_out || return 1 + u="$(prefixed_url "$p" "$base_url")" + if head_url_try "$u"; then + echo "$u" + return 0 + fi + done + return 1 +} + +pick_mihomo_asset_by_url() { + repo="$1" + tag="$2" + arch="$3" + channel="$4" + ver="${tag#v}" + candidates="" + + if [ "$channel" = "alpha" ] || [ "$channel" = "smart" ]; then + if [ "$arch" = "linux-amd64" ]; then + if [ "$channel" = "smart" ]; then + candidates="mihomo-linux-amd64-alpha-smart-*.gz mihomo-linux-amd64-compatible-alpha-smart-*.gz" + else + candidates="mihomo-linux-amd64-alpha-*.gz mihomo-linux-amd64-compatible-alpha-*.gz" + fi + else + [ "$channel" = "smart" ] && candidates="mihomo-${arch}-alpha-smart-*.gz" || candidates="mihomo-${arch}-alpha-*.gz" + fi + assets_html="$(fetch_expanded_asset_names "$repo" "$tag")" + if [ -n "$assets_html" ]; then + for a in $(printf '%s\n' "$assets_html"); do + for pat in $candidates; do + case "$a" in + $pat) + u="https://github.com/${repo}/releases/download/${tag}/${a}" + if pick_reachable_release_url "$u" >/dev/null 2>&1; then + echo "$a" + return 0 + fi + ;; + esac + done + done + fi + return 1 + fi + + if [ "$arch" = "linux-amd64" ]; then + candidates="mihomo-linux-amd64-v${ver}.gz mihomo-linux-amd64-compatible-v${ver}.gz" + else + candidates="mihomo-${arch}-v${ver}.gz" + fi + + for a in $candidates; do + u="https://github.com/${repo}/releases/download/${tag}/${a}" + if pick_reachable_release_url "$u" >/dev/null 2>&1; then + echo "$a" + return 0 + fi + done + + return 1 +} + +download_with_mirrors() { + base_url="$1" + outfile="$2" + verify_mode="${3:-gzip}" + proxy="$(detect_proxy)" + if [ -n "$proxy" ]; then + download_prefixes="__direct__ $(mirror_prefixes)" + else + download_prefixes="$(mirror_prefixes) __direct__" + fi + for p in $download_prefixes; do + [ "$p" = "__direct__" ] && p="" + ensure_not_timed_out || return 1 + u="$(prefixed_url "$p" "$base_url")" + i=1 + while [ "$i" -le "$ATTEMPTS_PER_MIRROR" ]; do + ensure_not_timed_out || return 1 + write_log "Downloading from ${u}" + rm -f "$outfile" 2>/dev/null + if download_file_try "$u" "$outfile" "$proxy"; then + case "$verify_mode" in + raw) + if [ -s "$outfile" ]; then + return 0 + fi + write_log "Downloaded file is empty from ${u}" + ;; + *) + if gzip -t "$outfile" >/dev/null 2>&1; then + return 0 + fi + write_log "Downloaded file is invalid gzip from ${u}" + ;; + esac + fi + sleep 1 + i=$((i + 1)) + done + done + return 1 +} + +pick_mihomo_asset() { + repo="$1" + tag="$2" + arch="$3" + channel="$4" + ver="${tag#v}" + candidates="" + release_assets="$(fetch_expanded_asset_names "$repo" "$tag")" + [ -z "$release_assets" ] && pick_mihomo_asset_by_url "$repo" "$tag" "$arch" "$channel" && return 0 + [ -z "$release_assets" ] && return 1 + + if [ "$channel" = "alpha" ] || [ "$channel" = "smart" ]; then + if [ "$arch" = "linux-amd64" ]; then + if [ "$channel" = "smart" ]; then + asset="$(printf '%s\n' "$release_assets" | sed -n 's#^\(mihomo-linux-amd64-alpha-smart-[0-9a-fA-F]\{7,\}\.gz\)$#\1#p' | head -n 1)" + [ -z "$asset" ] && asset="$(printf '%s\n' "$release_assets" | sed -n 's#^\(mihomo-linux-amd64-compatible-alpha-smart-[0-9a-fA-F]\{7,\}\.gz\)$#\1#p' | head -n 1)" + else + asset="$(printf '%s\n' "$release_assets" | sed -n 's#^\(mihomo-linux-amd64-alpha-[0-9a-fA-F]\{7,\}\.gz\)$#\1#p' | head -n 1)" + [ -z "$asset" ] && asset="$(printf '%s\n' "$release_assets" | sed -n 's#^\(mihomo-linux-amd64-compatible-alpha-[0-9a-fA-F]\{7,\}\.gz\)$#\1#p' | head -n 1)" + fi + else + if [ "$channel" = "smart" ]; then + asset="$(printf '%s\n' "$release_assets" | sed -n "s#^\\(mihomo-${arch}-alpha-smart-[0-9a-fA-F]\\{7,\\}\\.gz\\)$#\\1#p" | head -n 1)" + else + asset="$(printf '%s\n' "$release_assets" | sed -n "s#^\\(mihomo-${arch}-alpha-[0-9a-fA-F]\\{7,\\}\\.gz\\)$#\\1#p" | head -n 1)" + fi + fi + [ -n "$asset" ] && echo "$asset" && return 0 + pick_mihomo_asset_by_url "$repo" "$tag" "$arch" "$channel" + return $? + fi + + if [ "$arch" = "linux-amd64" ]; then + candidates="mihomo-linux-amd64-v${ver}.gz mihomo-linux-amd64-compatible-v${ver}.gz" + else + candidates="mihomo-${arch}-v${ver}.gz" + fi + + for a in $candidates; do + if printf '%s\n' "$release_assets" | grep -qx "$a"; then + echo "$a" + return 0 + fi + done + + pick_mihomo_asset_by_url "$repo" "$tag" "$arch" "$channel" + return $? + +} + +install_binary() { + local src dst dir staged + src="$1" + dst="$2" + dir="$(dirname "$dst")" + staged="$dir/.$(basename "$dst").tmp.$$" + mkdir -p "$dir" || return 1 + rm -f "$staged" + cp -f "$src" "$staged" || { + rm -f "$staged" + return 1 + } + chmod 755 "$staged" || { + rm -f "$staged" + return 1 + } + mv -f "$staged" "$dst" || { + rm -f "$staged" + return 1 + } +} + +backup_binary() { + dst="$1" + bak="${dst}.bak" + if [ -x "$dst" ]; then + cp -f "$dst" "$bak" 2>/dev/null || true + [ -f "$bak" ] || { write_log "备份旧内核失败(磁盘满?),跳过备份"; } + fi +} + +restore_binary() { + dst="$1" + bak="${dst}.bak" + if [ -f "$bak" ]; then + cp -f "$bak" "$dst" 2>/dev/null || return 1 + chmod 755 "$dst" 2>/dev/null || true + return 0 + fi + return 1 +} + +verify_binary() { + local bin + bin="$1" + [ -x "$bin" ] || return 1 + "$bin" -v >/dev/null 2>&1 && return 0 + "$bin" version >/dev/null 2>&1 && return 0 + return 1 +} + +verify_binary_version() { + local bin expected output actual + bin="$1" + expected="$2" + [ -n "$expected" ] || return 0 + output="$({ "$bin" version 2>/dev/null || "$bin" -v 2>/dev/null; } || true)" + [ -n "$output" ] || return 1 + actual="$(printf '%s\n' "$output" | sed -n 's/^[[:space:]]*sing-box[[:space:]][[:space:]]*version[[:space:]][[:space:]]*\([^[:space:]][^[:space:]]*\).*/\1/p' | head -n 1)" + [ -n "$actual" ] || return 1 + [ "$actual" = "$expected" ] +} + +installed_version_matches() { + version_file="$1" + expected_version="$2" + [ -r "$version_file" ] || return 1 + [ -n "$expected_version" ] || return 1 + [ "$(sed -n '1p' "$version_file" 2>/dev/null)" = "$expected_version" ] +} + +install_with_rollback() { + local tmpfile target expected_version + tmpfile="$1" + target="$2" + expected_version="${3:-}" + + chmod 755 "$tmpfile" 2>/dev/null + if ! verify_binary "$tmpfile" || ! verify_binary_version "$tmpfile" "$expected_version"; then + write_log "新内核预热校验失败,保留当前内核" + rm -f "$tmpfile" 2>/dev/null + return 1 + fi + + backup_binary "$target" + if ! install_binary "$tmpfile" "$target"; then + write_log "内核替换失败,回滚旧版本" + restore_binary "$target" >/dev/null 2>&1 || true + return 1 + fi + + rm -f "${target}.bak" 2>/dev/null + return 0 +} + +# Unit tests source the extraction helpers without entering the normal UCI and +# network download path. This hook has no effect in production execution. +if [ "${CLASHOO_CORE_DOWNLOAD_LIB_ONLY:-0}" = "1" ]; then + return 0 2>/dev/null || exit 0 +fi + +if ! acquire_core_download_lock; then + write_log "已有内核下载任务运行,拒绝并发执行" + exit 1 +fi +trap finalize EXIT + +# UCI template defaults to amd64-compatible. Auto-detect when MODELTYPE +# is missing, or when MODELTYPE is an amd64 variant but the device is not. +if [ -z "$MODELTYPE" ]; then + DETECTED="$(_auto_detect_modeltype)" + if [ -n "$DETECTED" ] && [ "$DETECTED" != "$MODELTYPE" ]; then + write_log "自动识别处理器架构:${DETECTED}" + MODELTYPE="$DETECTED" + uci set clashoo.config.download_core="$DETECTED" 2>/dev/null + uci commit clashoo 2>/dev/null + fi +elif _is_amd64_arch "$MODELTYPE"; then + DETECTED="$(_auto_detect_modeltype)" + if [ -n "$DETECTED" ] && [ "$DETECTED" != "$MODELTYPE" ] && ! _is_amd64_arch "$DETECTED"; then + write_log "UCI 架构为 ${MODELTYPE},但设备检测为 ${DETECTED},自动修正" + MODELTYPE="$DETECTED" + uci set clashoo.config.download_core="$DETECTED" 2>/dev/null + uci commit clashoo 2>/dev/null + fi +fi + +rm -f /tmp/clash.gz /tmp/clash /usr/share/clashoo/core_down_complete 2>/dev/null +touch /var/run/core_update 2>/dev/null +write_log "内核下载任务启动" +write_log "下载架构:${MODELTYPE:-未设置}" + +if [ -n "$CUSTOM_CORE_URL" ]; then + write_log "使用自定义内核下载链接" + URL="$CUSTOM_CORE_URL" + if [ "$CORETYPE" = "3" ]; then + TARGET="/usr/share/clashoo/bin/mihomo-alpha" + VERSION_FILE="/usr/share/clashoo/mihomo_version" + else + TARGET="/usr/share/clashoo/bin/mihomo-stable" + VERSION_FILE="/usr/share/clashoo/clash_meta_version" + fi + TAG="custom" + ASSET="custom" + VERSION_VALUE="custom-url" + + if ! download_file_try "$URL" /tmp/clash.gz "$(detect_proxy)"; then + write_log "自定义链接下载失败" + exit 1 + fi + + if ! gunzip -f /tmp/clash.gz; then + write_log "自定义内核解压失败" + exit 1 + fi + + if ! install_with_rollback /tmp/clash "$TARGET"; then + exit 1 + fi + CORE_INSTALLED=1 + printf '%s\n' "${VERSION_VALUE}" > "$VERSION_FILE" + touch /usr/share/clashoo/core_down_complete + write_log "内核更新完成(自定义链接)" + exit 0 +fi + +if [ "$CORETYPE" = "4" ] || [ "$CORETYPE" = "5" ]; then + SINGBOX_ARCH="$(map_singbox_arch "$MODELTYPE")" + OPENWRT_ARCH="$(detect_openwrt_arch)" + if [ "$CORETYPE" = "4" ]; then + write_log "已选择 sing-box 稳定版" + write_log "正在获取 GitHub Release 信息..." + TAG=$(fetch_latest_tag "SagerNet/sing-box") + [ -z "$TAG" ] && write_log "获取 sing-box 稳定版版本号失败" && exit 1 + else + write_log "已选择 sing-box 预发布版" + write_log "正在获取 GitHub Release 信息..." + TAG=$(fetch_prerelease_tag "SagerNet/sing-box") + [ -z "$TAG" ] && TAG=$(fetch_latest_tag "SagerNet/sing-box") + [ -z "$TAG" ] && write_log "获取 sing-box 预发布版版本号失败" && exit 1 + fi + VER="${TAG#v}" + SINGBOX_TARGET="$(singbox_target_path)" + installed=0 + + if [ -n "$OPENWRT_ARCH" ]; then + ASSET="sing-box_${VER}_openwrt_${OPENWRT_ARCH}.ipk" + URL="https://github.com/SagerNet/sing-box/releases/download/${TAG}/${ASSET}" + write_log "尝试提取 OpenWrt IPK 包: ${ASSET}" + if download_with_mirrors "$URL" /tmp/singbox-openwrt.ipk raw && install_singbox_openwrt_asset /tmp/singbox-openwrt.ipk "$SINGBOX_TARGET"; then + installed=1 + else + write_log "OpenWrt IPK 提取失败,尝试官方 musl tar 包" + fi + fi + + if [ "$installed" -ne 1 ]; then + for ASSET in "sing-box-${VER}-linux-${SINGBOX_ARCH}-musl.tar.gz" "sing-box-${VER}-linux-${SINGBOX_ARCH}.tar.gz"; do + URL="https://github.com/SagerNet/sing-box/releases/download/${TAG}/${ASSET}" + write_log "尝试下载 sing-box 归档包: ${ASSET}" + if download_with_mirrors "$URL" /tmp/singbox.tar.gz gzip && install_singbox_tar_asset /tmp/singbox.tar.gz "$SINGBOX_TARGET"; then + installed=1 + break + fi + done + fi + + if [ "$installed" -ne 1 ]; then + write_log "sing-box 下载或安装失败(可能是架构不匹配)" + exit 1 + fi + + CORE_INSTALLED=1 + # 稳定版与 Alpha 分路径保存,避免互相覆盖;版本文件保持兼容。 + if [ "$CORETYPE" = "4" ]; then + printf '%s\n' "$TAG" > "/usr/share/clashoo/singbox_stable_version" + else + printf '%s\n' "$TAG" > "/usr/share/clashoo/singbox_alpha_version" + fi + printf '%s\n' "$TAG" > "/usr/share/clashoo/singbox_version" + rm -f /tmp/singbox.tar.gz /tmp/singbox-openwrt.ipk + rm -rf /tmp/singbox-extract /tmp/singbox-openwrt-pkg /tmp/singbox-openwrt-root + touch /usr/share/clashoo/core_down_complete + write_log "sing-box 更新完成: $TAG" + exit 0 +fi + +if [ "$CORETYPE" = "1" ]; then + write_log "已选择内核通道:mihomo Smart 版(vernesong fork)" + TAG="Prerelease-Alpha" + write_log "正在获取 GitHub Release 信息..." + ASSET=$(pick_mihomo_asset "vernesong/mihomo" "$TAG" "$(map_mihomo_arch "$MODELTYPE")" "smart") + if [ -z "$ASSET" ]; then + ALT_TAG=$(fetch_prerelease_tag "vernesong/mihomo") + [ -n "$ALT_TAG" ] && TAG="$ALT_TAG" + ASSET=$(pick_mihomo_asset "vernesong/mihomo" "$TAG" "$(map_mihomo_arch "$MODELTYPE")" "smart") + fi + URL="https://github.com/vernesong/mihomo/releases/download/${TAG}/${ASSET}" + TARGET="/usr/bin/smart" + VERSION_FILE="/usr/share/clashoo/mihomo_version" + VERSION_VALUE="smart-$(printf '%s\n' "$ASSET" | sed -n 's#^mihomo-.*-alpha-smart-\([0-9a-fA-F]\{7,\}\)\.gz$#\1#p' | head -n 1)" + [ "$VERSION_VALUE" = "smart-" ] && VERSION_VALUE="smart-${TAG}" +elif [ "$CORETYPE" = "2" ]; then + write_log "已选择内核通道:稳定版" + write_log "正在获取 GitHub Release 信息..." + TAG=$(fetch_latest_tag "MetaCubeX/mihomo") + [ -z "$TAG" ] && write_log "获取稳定版版本号失败" && exit 1 + ASSET=$(pick_mihomo_asset "MetaCubeX/mihomo" "$TAG" "$(map_mihomo_arch "$MODELTYPE")" "stable") + URL="https://github.com/MetaCubeX/mihomo/releases/download/${TAG}/${ASSET}" + TARGET="/usr/share/clashoo/bin/mihomo-stable" + VERSION_FILE="/usr/share/clashoo/clash_meta_version" + VERSION_VALUE="$TAG" +else + write_log "已选择内核通道:预发布版" + TAG="Prerelease-Alpha" + write_log "正在获取 GitHub Release 信息..." + ASSET=$(pick_mihomo_asset "MetaCubeX/mihomo" "$TAG" "$(map_mihomo_arch "$MODELTYPE")" "alpha") + [ -z "$ASSET" ] && TAG=$(fetch_prerelease_tag "MetaCubeX/mihomo") + [ -z "$TAG" ] && TAG=$(fetch_latest_tag "MetaCubeX/mihomo") + [ -z "$TAG" ] && write_log "获取预发布版本号失败" && exit 1 + [ -z "$ASSET" ] && ASSET=$(pick_mihomo_asset "MetaCubeX/mihomo" "$TAG" "$(map_mihomo_arch "$MODELTYPE")" "alpha") + [ -z "$ASSET" ] && ASSET=$(pick_mihomo_asset "MetaCubeX/mihomo" "$TAG" "$(map_mihomo_arch "$MODELTYPE")" "stable") + URL="https://github.com/MetaCubeX/mihomo/releases/download/${TAG}/${ASSET}" + TARGET="/usr/share/clashoo/bin/mihomo-alpha" + VERSION_FILE="/usr/share/clashoo/mihomo_version" + VERSION_VALUE="$(printf '%s\n' "$ASSET" | sed -n 's#^mihomo-.*-alpha-\([0-9a-fA-F]\{7,\}\)\.gz$#\1#p' | head -n 1)" + [ -z "$VERSION_VALUE" ] && VERSION_VALUE="$TAG" +fi + +if [ -z "$TAG" ]; then + write_log "内核版本检查失败" + exit 1 +fi + +if [ -z "$ASSET" ]; then + write_log "未找到对应架构的内核文件(${MODELTYPE:-unknown} / ${TAG:-unknown})" + exit 1 +fi +write_log "版本标签:$TAG" +write_log "匹配内核文件:$ASSET" + +if [ -n "$TARGET_DCORE" ] && installed_version_matches "$VERSION_FILE" "$VERSION_VALUE" && verify_binary "$TARGET"; then + write_log "内核已是最新版本:$VERSION_VALUE" + exit 0 +fi + +write_log "开始下载内核" +if ! download_with_mirrors "$URL" /tmp/clash.gz; then + write_log "内核下载失败:所有镜像源均不可达(已尝试 gh-proxy.com / ghfast.top / 裸 GitHub)" + write_log "可设置自定义镜像前缀:uci set clashoo.config.core_mirror_prefix='https://your.mirror/' && uci commit clashoo" + exit 1 +fi + +if ! gunzip -f /tmp/clash.gz; then + write_log "内核解压失败" + exit 1 +fi + +if ! install_with_rollback /tmp/clash "$TARGET"; then + exit 1 +fi +CORE_INSTALLED=1 +printf '%s\n' "${VERSION_VALUE:-$TAG}" > "$VERSION_FILE" +touch /usr/share/clashoo/core_down_complete +write_log "内核更新完成" diff --git a/clashoo/files/usr/share/clashoo/update/geoip.sh b/clashoo/files/usr/share/clashoo/update/geoip.sh new file mode 100755 index 00000000..9c699e30 --- /dev/null +++ b/clashoo/files/usr/share/clashoo/update/geoip.sh @@ -0,0 +1,282 @@ +#!/bin/sh + +LOG_FILE="/tmp/geoip_update.txt" +TMP_DIR="/tmp/clash_geoip_$$" +MMDB_MIN_SIZE=2000000 +GEOSITE_MIN_SIZE=2000000 + +geoip_source="$(uci -q get clashoo.config.geoip_source 2>/dev/null)" +license_key="$(uci -q get clashoo.config.license_key 2>/dev/null)" + +cfg_mmdb_url="$(uci -q get clashoo.config.geoip_mmdb_url 2>/dev/null)" +cfg_geosite_url="$(uci -q get clashoo.config.geosite_url 2>/dev/null)" +cfg_geoip_dat_url="$(uci -q get clashoo.config.geoip_dat_url 2>/dev/null)" +cfg_geoip_asn_url="$(uci -q get clashoo.config.geoip_asn_url 2>/dev/null)" +require_geosite=0 +[ "$1" = "--require-geosite" ] && require_geosite=1 + +DEFAULT_MMDB_URL="https://raw.githubusercontent.com/Loyalsoldier/geoip/release/Country.mmdb" +DEFAULT_GEOSITE_URL="https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/release/geosite.dat" +DEFAULT_GEOIP_DAT_URL="https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/release/geoip.dat" +DEFAULT_GEOIP_ASN_URL="https://github.com/xishang0128/geoip/releases/download/latest/GeoLite2-ASN.mmdb" + +LOYALSOLDIER_MMDB_URL="https://raw.githubusercontent.com/Loyalsoldier/geoip/release/Country.mmdb" +LOYALSOLDIER_GEOSITE_URL="https://raw.githubusercontent.com/Loyalsoldier/v2ray-rules-dat/release/geosite.dat" +LOYALSOLDIER_GEOIP_DAT_URL="https://raw.githubusercontent.com/Loyalsoldier/v2ray-rules-dat/release/geoip.dat" + +# v2fly ships no Country.mmdb — borrow Loyalsoldier's for the mmdb slot +V2FLY_GEOSITE_URL="https://github.com/v2fly/domain-list-community/releases/latest/download/dlc.dat" +V2FLY_GEOIP_DAT_URL="https://github.com/v2fly/geoip/releases/latest/download/geoip.dat" + +log() { + echo " $(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG_FILE" +} + +cleanup() { + rm -rf "$TMP_DIR" >/dev/null 2>&1 + rm -f /var/run/geoip_update >/dev/null 2>&1 +} + +config_needs_geosite() { + _cfg="$(uci -q get clashoo.config.use_config 2>/dev/null)" + if [ -s "$_cfg" ] && grep -Eq "^[[:space:]]*-[[:space:]]*[\"']?GEOSITE,|^[[:space:]]*geosite:[[:space:]]*|^[[:space:]]*'geosite:|^[[:space:]]*-[[:space:]]*[\"']?geosite:" "$_cfg"; then + return 0 + fi + _cfg="/etc/clashoo/config.yaml" + [ -f "$_cfg" ] || return 1 + grep -Eq "^[[:space:]]*-[[:space:]]*[\"']?GEOSITE,|^[[:space:]]*geosite:[[:space:]]*|^[[:space:]]*'geosite:|^[[:space:]]*-[[:space:]]*[\"']?geosite:" "$_cfg" +} + +# Shared port detection (handles smart core + sing-box profile port); keep the +# local anonymity probe so we skip a coexisting plugin's auth-locked proxy. +. /usr/share/clashoo/update/proxy_lib.sh +detect_proxy() { + proxy="$(clashoo_detect_proxy)" + [ -n "$proxy" ] || return 0 + # Only use it if it actually proxies anonymously. A coexisting plugin + # (e.g. OpenClash on the same 7890) may hold this port behind auth and + # answer HTTP 407 — in that case fall through to the mirrors instead. + if command -v curl >/dev/null 2>&1; then + curl -fsS --proxy "$proxy" --connect-timeout 3 --max-time 5 -o /dev/null \ + http://www.gstatic.com/generate_204 >/dev/null 2>&1 || return 0 + fi + echo "$proxy" +} + +_fetch() { + url="$1"; target="$2"; proxy="$3" + if command -v curl >/dev/null 2>&1; then + if [ -n "$proxy" ]; then + curl --proxy "$proxy" -fsSL --connect-timeout 15 --max-time 120 -A "Clash/OpenWRT" "$url" -o "$target" + else + curl -fsSL --connect-timeout 15 --max-time 120 -A "Clash/OpenWRT" "$url" -o "$target" + fi + return $? + fi + wget -q --timeout=120 --user-agent="Clash/OpenWRT" "$url" -O "$target" +} + +# 多源拉取:本机代理 → 国内镜像(gh-proxy / ghfast / jsdelivr)→ 原 URL 兜底 +download_to() { + url="$1"; target="$2" + [ -z "$url" ] && return 1 + + proxy="$(detect_proxy)" + + if [ -n "$proxy" ]; then + _fetch "$url" "$target" "$proxy" && return 0 + fi + + case "$url" in + https://raw.githubusercontent.com/*) + rest="${url#https://raw.githubusercontent.com/}" + owner="${rest%%/*}"; r1="${rest#*/}" + repo="${r1%%/*}"; r2="${r1#*/}" + branch="${r2%%/*}"; path="${r2#*/}" + for m in \ + "https://gh-proxy.com/${url}" \ + "https://ghfast.top/${url}" \ + "https://cdn.jsdelivr.net/gh/${owner}/${repo}@${branch}/${path}"; do + _fetch "$m" "$target" "" && return 0 + done + ;; + https://github.com/*) + for m in \ + "https://gh-proxy.com/${url}" \ + "https://ghfast.top/${url}"; do + _fetch "$m" "$target" "" && return 0 + done + ;; + esac + + _fetch "$url" "$target" "" +} + +download_optional() { + url="$1" + target="$2" + name="$3" + tmp_target="${TMP_DIR}/$(basename "$target").tmp" + if [ -z "$url" ]; then + log "$name skip (url empty)" + return 0 + fi + rm -f "$tmp_target" >/dev/null 2>&1 + if download_to "$url" "$tmp_target"; then + mv -f "$tmp_target" "$target" >/dev/null 2>&1 || return 1 + chmod 644 "$target" >/dev/null 2>&1 + log "$name updated" + return 0 + fi + rm -f "$tmp_target" >/dev/null 2>&1 + log "$name update failed" + return 0 +} + +download_geosite() { + url="$1" + tmp_target="${TMP_DIR}/GeoSite.dat.tmp" + size=0 + if [ -z "$url" ]; then + log "GeoSite.dat skip (url empty)" + return 1 + fi + rm -f "$tmp_target" >/dev/null 2>&1 + if ! download_to "$url" "$tmp_target"; then + rm -f "$tmp_target" >/dev/null 2>&1 + log "GeoSite.dat update failed" + return 1 + fi + size=$(wc -c <"$tmp_target" 2>/dev/null) + if [ -z "$size" ] || [ "$size" -lt "$GEOSITE_MIN_SIZE" ]; then + log "GeoSite.dat download invalid or incomplete (${size:-0} bytes)" + rm -f "$tmp_target" >/dev/null 2>&1 + return 1 + fi + mv -f "$tmp_target" /etc/clashoo/GeoSite.dat >/dev/null 2>&1 || return 1 + cp -f /etc/clashoo/GeoSite.dat /etc/clashoo/geosite.dat >/dev/null 2>&1 || true + chmod 644 /etc/clashoo/GeoSite.dat /etc/clashoo/geosite.dat >/dev/null 2>&1 + log "GeoSite.dat updated" + return 0 +} + +download_mmdb() { + url="$1" + tmp_target="${TMP_DIR}/Country.mmdb.tmp" + size=0 + rm -f "$tmp_target" >/dev/null 2>&1 + if ! download_to "$url" "$tmp_target"; then + rm -f "$tmp_target" >/dev/null 2>&1 + return 1 + fi + size=$(wc -c <"$tmp_target" 2>/dev/null) + if [ -z "$size" ] || [ "$size" -lt "$MMDB_MIN_SIZE" ]; then + log "Country.mmdb download invalid or incomplete (${size:-0} bytes)" + rm -f "$tmp_target" >/dev/null 2>&1 + return 1 + fi + mv -f "$tmp_target" /etc/clashoo/Country.mmdb >/dev/null 2>&1 || return 1 + chmod 644 /etc/clashoo/Country.mmdb >/dev/null 2>&1 + return 0 +} + +trap cleanup EXIT INT TERM +mkdir -p "$TMP_DIR" /etc/clashoo >/dev/null 2>&1 + +rm -f /var/run/geoip_down_complete >/dev/null 2>&1 +: > /var/run/geoip_update +: > "$LOG_FILE" +log "GeoIP 更新任务启动" + +mmdb_url="" +geosite_url="" +geoip_dat_url="" +geoip_asn_url="" + +case "$geoip_source" in + 1) + if [ -z "$license_key" ]; then + log "MaxMind source selected but license key is empty" + exit 1 + fi + log "Updating Country.mmdb from MaxMind" + if ! download_to "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-Country&license_key=${license_key}&suffix=tar.gz" "$TMP_DIR/geoip.tar.gz"; then + log "MaxMind download failed" + exit 1 + fi + if ! tar zxf "$TMP_DIR/geoip.tar.gz" -C "$TMP_DIR" >/dev/null 2>&1; then + log "MaxMind archive extract failed" + exit 1 + fi + mmdb_file="$(ls "$TMP_DIR"/GeoLite2-Country_*/GeoLite2-Country.mmdb 2>/dev/null | head -n 1)" + if [ -z "$mmdb_file" ] || [ ! -f "$mmdb_file" ]; then + log "MaxMind Country.mmdb not found in archive" + exit 1 + fi + cp -f "$mmdb_file" /etc/clashoo/Country.mmdb + chmod 644 /etc/clashoo/Country.mmdb >/dev/null 2>&1 + log "Country.mmdb updated" + ;; + 3) + # Loyalsoldier (default) + mmdb_url="$LOYALSOLDIER_MMDB_URL" + geosite_url="$LOYALSOLDIER_GEOSITE_URL" + geoip_dat_url="$LOYALSOLDIER_GEOIP_DAT_URL" + geoip_asn_url="$DEFAULT_GEOIP_ASN_URL" + ;; + 5) + # v2fly (mmdb borrowed from Loyalsoldier) + mmdb_url="$LOYALSOLDIER_MMDB_URL" + geosite_url="$V2FLY_GEOSITE_URL" + geoip_dat_url="$V2FLY_GEOIP_DAT_URL" + geoip_asn_url="$DEFAULT_GEOIP_ASN_URL" + ;; + 4) + # custom — asn falls back to default when left blank + mmdb_url="$cfg_mmdb_url" + geosite_url="$cfg_geosite_url" + geoip_dat_url="$cfg_geoip_dat_url" + geoip_asn_url="${cfg_geoip_asn_url:-$DEFAULT_GEOIP_ASN_URL}" + ;; + *) + mmdb_url="${cfg_mmdb_url:-$DEFAULT_MMDB_URL}" + geosite_url="${cfg_geosite_url:-$DEFAULT_GEOSITE_URL}" + geoip_dat_url="${cfg_geoip_dat_url:-$DEFAULT_GEOIP_DAT_URL}" + geoip_asn_url="${cfg_geoip_asn_url:-$DEFAULT_GEOIP_ASN_URL}" + ;; +esac + +if [ "$geoip_source" != "1" ]; then + log "Updating Country.mmdb" + if ! download_mmdb "$mmdb_url"; then + log "Country.mmdb download failed" + exit 1 + fi + log "Country.mmdb updated" + + if [ "$require_geosite" -eq 1 ] || config_needs_geosite; then + if ! download_geosite "$geosite_url"; then + exit 1 + fi + else + log "GeoSite.dat skip (no geosite reference in config)" + fi + download_optional "$geoip_dat_url" /etc/clashoo/geoip.dat "geoip.dat" + download_optional "$geoip_asn_url" /etc/clashoo/GeoLite2-ASN.mmdb "GeoLite2-ASN.mmdb" +fi + +if [ "$geoip_source" = "1" ] && { [ "$require_geosite" -eq 1 ] || config_needs_geosite; }; then + geosite_url="${cfg_geosite_url:-$DEFAULT_GEOSITE_URL}" + if ! download_geosite "$geosite_url"; then + exit 1 + fi +fi + +touch /var/run/geoip_down_complete >/dev/null 2>&1 + +if pidof mihomo >/dev/null 2>&1 || pidof clash-meta >/dev/null 2>&1; then + log "GeoIP update completed, apply on next Clashoo restart" +fi + +log "GeoIP update completed" +exit 0 diff --git a/clashoo/files/usr/share/clashoo/update/lgbm_update.sh b/clashoo/files/usr/share/clashoo/update/lgbm_update.sh new file mode 100755 index 00000000..fa17a30c --- /dev/null +++ b/clashoo/files/usr/share/clashoo/update/lgbm_update.sh @@ -0,0 +1,52 @@ +#!/bin/sh + +MODEL_PATH="/etc/clashoo/Model.bin" +TMP_PATH="/tmp/clash_Model.bin" +LOG_FILE="/tmp/lgbm_update.log" + +log() { + echo " $(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG_FILE" +} + +: > "$LOG_FILE" + +DOWNLOAD_URL=$(uci get clashoo.config.smart_lgbm_url 2>/dev/null) +[ -z "$DOWNLOAD_URL" ] && DOWNLOAD_URL="https://github.com/vernesong/mihomo/releases/download/LightGBM-Model/Model.bin" + +log "Start downloading LightGBM model from: $DOWNLOAD_URL" + +mkdir -p /etc/clashoo + +rm -f "$TMP_PATH" 2>/dev/null +if command -v curl >/dev/null 2>&1; then + curl -fL --connect-timeout 15 --max-time 120 -A "Clash/OpenWRT" "$DOWNLOAD_URL" -o "$TMP_PATH" 2>/dev/null + dl_rc=$? +elif command -v wget >/dev/null 2>&1; then + wget -q --timeout=30 -U "Clash/OpenWRT" "$DOWNLOAD_URL" -O "$TMP_PATH" 2>/dev/null + dl_rc=$? +else + log "No curl or wget found" + exit 1 +fi + +if [ "$dl_rc" -ne 0 ] || [ ! -s "$TMP_PATH" ]; then + log "Download failed (rc=$dl_rc)" + rm -f "$TMP_PATH" + exit 1 +fi + +if [ -f "$MODEL_PATH" ] && cmp -s "$TMP_PATH" "$MODEL_PATH"; then + log "Model unchanged, no update needed" + rm -f "$TMP_PATH" + exit 0 +fi + +mv "$TMP_PATH" "$MODEL_PATH" 2>/dev/null +if [ $? -ne 0 ]; then + log "Failed to install model to $MODEL_PATH" + rm -f "$TMP_PATH" + exit 1 +fi + +log "LightGBM model updated successfully" +exit 0 diff --git a/clashoo/files/usr/share/clashoo/update/maintenance_cron.sh b/clashoo/files/usr/share/clashoo/update/maintenance_cron.sh new file mode 100755 index 00000000..20eed4db --- /dev/null +++ b/clashoo/files/usr/share/clashoo/update/maintenance_cron.sh @@ -0,0 +1,37 @@ +#!/bin/sh + +STATE_DIR="${CLASHOO_STATE_DIR:-/usr/share/clashbackup}" +UPDATE_SCRIPT="${CLASHOO_UPDATE_SCRIPT:-/usr/share/clashoo/update/update_all.sh}" +LOG_FILE="${CLASHOO_LOG_FILE:-/usr/share/clashoo/clashoo.txt}" +LOCK_DIR="${CLASHOO_LOCK_DIR:-/tmp/clashoo_maintenance.lock}" +NOW="${CLASHOO_NOW:-$(date +%s)}" + +stamp() { + tmp="$STATE_DIR/$1.tmp.$$" + printf 'last_run=%s\n' "$NOW" >"$tmp" && mv -f "$tmp" "$STATE_DIR/$1" +} + +due() { + hours="$(uci -q get "clashoo.config.$2")" + echo "$hours" | grep -Eq '^[0-9]+$' && [ "$hours" -ge 1 ] || hours=12 + last="$(sed -n 's/^last_run=//p' "$STATE_DIR/$1" 2>/dev/null | head -1)" + if ! echo "$last" | grep -Eq '^[0-9]+$' || [ "$last" -gt "$NOW" ]; then + stamp "$1" + return 1 + fi + [ $((NOW - last)) -ge $((hours * 3600)) ] +} + +mkdir -p "$STATE_DIR" || exit 0 +mkdir "$LOCK_DIR" 2>/dev/null || exit 0 +trap 'rmdir "$LOCK_DIR" >/dev/null 2>&1' EXIT INT TERM + +if [ "$(uci -q get clashoo.config.auto_update)" = "1" ] && due rule_update.status auto_update_time; then + CLASHOO_STATE_DIR="$STATE_DIR" sh "$UPDATE_SCRIPT" >/dev/null 2>&1 +fi + +if [ "$(uci -q get clashoo.config.auto_clear_log)" = "1" ] && due log_cleanup.status clear_time; then + : >"$LOG_FILE" + sh /usr/share/clashoo/lib/core_log_guard.sh >/dev/null 2>&1 + stamp log_cleanup.status +fi diff --git a/clashoo/files/usr/share/clashoo/update/panel_download.sh b/clashoo/files/usr/share/clashoo/update/panel_download.sh new file mode 100755 index 00000000..96c241a6 --- /dev/null +++ b/clashoo/files/usr/share/clashoo/update/panel_download.sh @@ -0,0 +1,204 @@ +#!/bin/sh + +REAL_LOG="/usr/share/clashoo/clashoo_real.txt" +panel="$1" +lang="$(uci -q get luci.main.lang 2>/dev/null)" +STATE_FILE="/tmp/clash_panel_download_state" +RUN_FILE="/var/run/panel_downloading" +DASHBOARD_LINK="/etc/clashoo/dashboard" + +[ -n "$panel" ] || panel="$(uci -q get clashoo.config.dashboard_panel 2>/dev/null)" +[ -n "$panel" ] || panel="zashboard" + +case "$panel" in + metacubexd) + URLS="https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip" + TARGET_DIR="/etc/clashoo/dashboard-metacubexd" + ;; + yacd) + URLS="https://github.com/MetaCubeX/Yacd-meta/archive/refs/heads/gh-pages.zip https://github.com/haishanh/yacd/archive/refs/heads/gh-pages.zip" + TARGET_DIR="/etc/clashoo/dashboard-yacd" + ;; + zashboard) + URLS="https://github.com/Zephyruso/zashboard/releases/latest/download/dist.zip https://github.com/Zephyruso/zashboard/releases/latest/download/dist-cdn-fonts.zip" + TARGET_DIR="/etc/clashoo/dashboard-zashboard" + ;; + razord) + URLS="https://github.com/MetaCubeX/Razord-meta/archive/refs/heads/gh-pages.zip https://github.com/ayanamist/clash-dashboard/archive/refs/heads/gh-pages.zip" + TARGET_DIR="/etc/clashoo/dashboard-razord" + ;; + *) + URLS="https://github.com/Zephyruso/zashboard/releases/latest/download/dist.zip https://github.com/Zephyruso/zashboard/releases/latest/download/dist-cdn-fonts.zip" + TARGET_DIR="/etc/clashoo/dashboard-zashboard" + panel="zashboard" + ;; +esac + +TMP_ROOT="/tmp/clash_panel_${panel}_$$" +ZIP_FILE="$TMP_ROOT/panel.zip" +UNPACK_DIR="$TMP_ROOT/unpack" + +UPDATE_LOG="/tmp/clash_update.txt" + +update_log() { + [ -n "$1" ] || return 0 + printf '%s - %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$1" >>"$UPDATE_LOG" +} + +log_msg() { + if [ "$lang" = "en" ]; then + echo "$1" >"$REAL_LOG" + [ "$3" = "1" ] || update_log "$1" + else + echo "$2" >"$REAL_LOG" + [ "$3" = "1" ] || update_log "$2" + fi +} + +set_state() { + state="$1" + msg="$2" + echo "${state}:${panel}:${msg}" >"$STATE_FILE" +} + +cleanup() { + rm -rf "$TMP_ROOT" >/dev/null 2>&1 + rm -f "$RUN_FILE" >/dev/null 2>&1 +} + +# Route the panel download through the running core in kernel-only mode +# (shared logic in proxy_lib.sh). Normal mode returns empty -> TPROXY. +. /usr/share/clashoo/update/proxy_lib.sh +detect_proxy() { clashoo_detect_proxy; } + +download_zip() { + proxy="$(detect_proxy)" + for url in $URLS; do + if [ -n "$proxy" ] && command -v curl >/dev/null 2>&1 && + curl -fsSL --connect-timeout 15 --max-time 300 --retry 1 \ + --proxy "$proxy" -A "Clash/OpenWRT" "$url" -o "$ZIP_FILE"; then + return 0 + fi + if wget -q --timeout=60 --user-agent="Clash/OpenWRT" "$url" -O "$ZIP_FILE"; then + return 0 + fi + done + return 1 +} + +validate_zip_entries() { + entries="$TMP_ROOT/entries.txt" + if command -v unzip >/dev/null 2>&1; then + unzip -Z1 "$ZIP_FILE" >"$entries" 2>/dev/null || return 1 + elif command -v bsdtar >/dev/null 2>&1; then + bsdtar -tf "$ZIP_FILE" >"$entries" 2>/dev/null || return 1 + else + return 1 + fi + [ -s "$entries" ] || return 1 + awk ' + /^\// || /^[A-Za-z]:/ || /(^|\/)\.\.(\/|$)/ || /\\/ || /\r/ { bad = 1 } + END { exit bad } + ' "$entries" +} + +extract_zip() { + if command -v unzip >/dev/null 2>&1; then + unzip -oq "$ZIP_FILE" -d "$UNPACK_DIR" >/dev/null 2>&1 + return $? + fi + + if command -v busybox >/dev/null 2>&1 && busybox --list 2>/dev/null | grep -qx "unzip"; then + busybox unzip -oq "$ZIP_FILE" -d "$UNPACK_DIR" >/dev/null 2>&1 + return $? + fi + + if command -v bsdtar >/dev/null 2>&1; then + bsdtar -xf "$ZIP_FILE" -C "$UNPACK_DIR" >/dev/null 2>&1 + return $? + fi + + return 2 +} + +find_web_root() { + if [ -f "$UNPACK_DIR/index.html" ]; then + echo "$UNPACK_DIR" + return 0 + fi + + index_file="$(find "$UNPACK_DIR" -type f -name index.html 2>/dev/null | head -n 1)" + if [ -n "$index_file" ]; then + dirname "$index_file" + return 0 + fi + + return 1 +} + +activate_panel() { + rm -rf "$DASHBOARD_LINK" >/dev/null 2>&1 + ln -s "$TARGET_DIR" "$DASHBOARD_LINK" >/dev/null 2>&1 || cp -a "$TARGET_DIR" "$DASHBOARD_LINK" >/dev/null 2>&1 + rm -rf /www/luci-static/yacd >/dev/null 2>&1 + if [ "$panel" = "yacd" ]; then + ln -s "$TARGET_DIR" /www/luci-static/yacd >/dev/null 2>&1 + fi +} + +trap cleanup EXIT INT TERM +mkdir -p "$UNPACK_DIR" "$TARGET_DIR" >/dev/null 2>&1 +touch "$RUN_FILE" +set_state "downloading" "开始下载" + +log_msg "Downloading dashboard panel..." "正在下载面板..." +if ! download_zip; then + set_state "error" "下载失败" + log_msg "Dashboard panel download failed" "面板下载失败" + exit 1 +fi + +if ! validate_zip_entries; then + set_state "error" "压缩包路径无效" + log_msg "Dashboard panel archive has unsafe entries" "面板压缩包含不安全路径" + exit 1 +fi + +extract_zip +extract_rc=$? +if [ "$extract_rc" -ne 0 ]; then + if [ "$extract_rc" -eq 2 ]; then + set_state "error" "缺少解压工具" + log_msg "Panel unzip tool not found" "缺少解压工具,无法安装面板" + else + set_state "error" "解压失败" + log_msg "Dashboard panel unzip failed" "面板解压失败" + fi + exit 1 +fi + +if find "$UNPACK_DIR" ! -type d ! -type f | grep -q .; then + set_state "error" "压缩包文件类型无效" + log_msg "Dashboard panel archive has unsafe file types" "面板压缩包含不安全文件类型" + exit 1 +fi + +SRC_DIR="$(find_web_root)" +if [ -z "$SRC_DIR" ] || [ ! -f "$SRC_DIR/index.html" ]; then + set_state "error" "文件无效" + log_msg "Dashboard panel content invalid" "面板文件无效" + exit 1 +fi + +rm -rf "$TARGET_DIR"/* >/dev/null 2>&1 +cp -a "$SRC_DIR"/. "$TARGET_DIR"/ >/dev/null 2>&1 + +activate_panel + +uci set clashoo.config.dashboard_panel="$panel" >/dev/null 2>&1 +uci commit clashoo >/dev/null 2>&1 + +set_state "success" "安装成功" +log_msg "Dashboard panel installed" "面板安装完成" +sleep 1 +log_msg "Clashoo" "Clashoo" 1 +exit 0 diff --git a/clashoo/files/usr/share/clashoo/update/proxy_lib.sh b/clashoo/files/usr/share/clashoo/update/proxy_lib.sh new file mode 100644 index 00000000..e9664420 --- /dev/null +++ b/clashoo/files/usr/share/clashoo/update/proxy_lib.sh @@ -0,0 +1,80 @@ +# shellcheck shell=sh +# Clashoo's sing-box is private; HomeProxy owns the public system binary. +CLASHOO_SINGBOX_BIN_DIR="/usr/share/clashoo/bin" +CLASHOO_SINGBOX_RUNTIME_CONFIG="/var/run/clashoo/sing-box/config.json" + +# Shared proxy detection for clashoo's own outbound requests. +# +# clashoo's self-initiated downloads (update checks, component/panel/core/geoip +# downloads, subscriptions) and health checks must reach GitHub through the +# running core, otherwise they leak out direct and stall behind the GFW. Route +# them through the core's mixed port whenever a core is running — in BOTH modes: +# - core-only mode deliberately skips TPROXY to coexist with other plugins; +# - normal mode used to rely on transparent redirect, but the router's OWN +# output redirect is fragile (e.g. PPPoE/GL.iNet stacks where Redirect to +# loopback never completes — issue #25), so don't depend on it. The explicit +# mixed port works on every network stack. +# When no core is running (e.g. bootstrapping the very first core download), +# returns empty so the caller falls back to direct / mirror sources. +# +# Port source depends on the running Clashoo kernel. The liveness gate uses the +# procd owner instead of a binary name, because other proxy plugins may run the +# same binaries: +# sing-box -> private runtime config mixed inbound +# (an imported profile may carry its own port) +# mihomo / clash-meta / smart -> /etc/clashoo/config.yaml mixed-port +# (a custom config's port may differ from uci) +# uci mixed_port is the last-resort fallback for both. +clashoo_running_core_type() { + _cdp_type="$(uci -q get clashoo.config.core_type 2>/dev/null)" + case "$_cdp_type" in + singbox) + _cdp_service="clashoo" + _cdp_filter='@.clashoo.instances.singbox.pid' + ;; + *) + _cdp_type="mihomo" + _cdp_service="clashoo" + _cdp_filter='@.clashoo.instances.*.pid' + ;; + esac + + _cdp_pid="$(ubus call service list "{\"name\":\"$_cdp_service\",\"verbose\":true}" 2>/dev/null \ + | jsonfilter -e "$_cdp_filter" 2>/dev/null | head -n1)" + [ -n "$_cdp_pid" ] && [ -d "/proc/$_cdp_pid" ] || return 1 + if [ "$_cdp_type" = "singbox" ]; then + local _cdp_exe _cdp_cmdline + _cdp_exe="$(readlink -f "/proc/$_cdp_pid/exe" 2>/dev/null)" + case "$_cdp_exe" in + "$CLASHOO_SINGBOX_BIN_DIR/sing-box-stable"|"$CLASHOO_SINGBOX_BIN_DIR/sing-box-alpha") ;; + *) + _cdp_cmdline="$(tr '\000' ' ' < "/proc/$_cdp_pid/cmdline" 2>/dev/null)" + case "$_cdp_cmdline" in + "$CLASHOO_SINGBOX_BIN_DIR/sing-box-stable"|"$CLASHOO_SINGBOX_BIN_DIR/sing-box-stable "*|"$CLASHOO_SINGBOX_BIN_DIR/sing-box-alpha"|"$CLASHOO_SINGBOX_BIN_DIR/sing-box-alpha "*) ;; + *) return 1 ;; + esac + ;; + esac + fi + printf '%s' "$_cdp_type" +} + +clashoo_detect_proxy() { + _cdp_type="$(clashoo_running_core_type)" || return 0 + _cdp_port="" + if [ "$_cdp_type" = "singbox" ]; then + _cdp_port="$(jsonfilter -i "$CLASHOO_SINGBOX_RUNTIME_CONFIG" \ + -e '@.inbounds[@.type="mixed"].listen_port' 2>/dev/null | head -n 1)" + else + # prefer mixed-port (HTTP+SOCKS) over plain port; a single two-pattern + # sed would return whichever appears first by line order, so loop by key + for _cdp_key in mixed-port port socks-port; do + _cdp_port="$(sed -n "s/^[[:space:]]*${_cdp_key}:[[:space:]]*\([0-9][0-9]*\).*/\1/p" \ + /etc/clashoo/config.yaml 2>/dev/null | head -n 1)" + [ -n "$_cdp_port" ] && break + done + fi + [ -n "$_cdp_port" ] || _cdp_port="$(uci -q get clashoo.config.mixed_port 2>/dev/null)" + [ -n "$_cdp_port" ] || return 0 + printf 'http://127.0.0.1:%s' "$_cdp_port" +} diff --git a/clashoo/files/usr/share/clashoo/update/subscription_update.sh b/clashoo/files/usr/share/clashoo/update/subscription_update.sh new file mode 100755 index 00000000..60827ed4 --- /dev/null +++ b/clashoo/files/usr/share/clashoo/update/subscription_update.sh @@ -0,0 +1,385 @@ +#!/bin/sh + +SUB_DIR="${CLASHOO_SUB_DIR:-/usr/share/clashoo/config/sub}" +SINGBOX_DIR="${CLASHOO_SINGBOX_DIR:-/usr/share/clashoo/config/singbox}" +BACKUP_DIR="${CLASHOO_BACKUP_DIR:-/usr/share/clashbackup}" +TEMPLATE_DIR="${CLASHOO_TEMPLATE_DIR:-/usr/share/clashoo/config/custom}" +LIST_FILE="${CLASHOO_LIST_FILE:-$BACKUP_DIR/confit_list.conf}" +BIND_FILE="${CLASHOO_BIND_FILE:-$BACKUP_DIR/template_bindings.conf}" +STATUS_FILE="${CLASHOO_STATUS_FILE:-$BACKUP_DIR/subscription_update.status}" +LOCK_DIR="${CLASHOO_LOCK_DIR:-/tmp/clashoo_subscription_update.lock}" +UPDATE_LOG="${CLASHOO_UPDATE_LOG:-/tmp/clash_update.txt}" +SERVICE_CMD="${CLASHOO_SERVICE_CMD:-/etc/init.d/clashoo}" +TMP_DIR="${CLASHOO_TMP_DIR:-/tmp}" +DEFAULT_SUB_UA='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36' +DOWNLOAD_ERROR="" + +# Subscriptions are fetched direct first (many airports are domestic / geo-fence +# foreign exit IPs). Only used as a last-resort fallback, see download_to. +. /usr/share/clashoo/update/proxy_lib.sh + +updated=0 +unchanged=0 +failed=0 +skipped=0 +restart_needed=0 +started_at="$(date +%s)" +message="" +record_status=0 +[ "$1" = "--all" ] && record_status=1 + +log_update() { + printf ' %s - %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$1" >>"$UPDATE_LOG" +} + +write_status() { + local tmp + mkdir -p "$(dirname "$STATUS_FILE")" >/dev/null 2>&1 + tmp="${STATUS_FILE}.tmp.$$" + cat >"$tmp" </dev/null 2>&1 +} + +if ! mkdir "$LOCK_DIR" >/dev/null 2>&1; then + exit 75 +fi +trap cleanup EXIT INT TERM +[ "$record_status" = "1" ] && write_status 1 0 + +safe_name() { + case "$1" in + ''|*/*|*..*) return 1 ;; + esac + return 0 +} + +extract_host() { + printf '%s' "$1" | sed -e 's#^[a-zA-Z0-9+.-]*://##' -e 's#/.*$##' -e 's#:.*$##' -e 's#.*@##' +} + +resolve_via() { + local host dns + host="$1" + dns="$2" + nslookup "$host" "$dns" 2>/dev/null | awk ' + /^Address/ { + ip = $NF + if (ip ~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/ && + ip !~ /^127\./ && ip !~ /^0\./ && ip !~ /^198\.18\./ && + ip != "8.8.8.8" && ip != "1.1.1.1" && + ip != "223.5.5.5" && ip != "119.29.29.29") { + print ip + exit + } + }' +} + +curl_download() { + local url out hdr ua host ip + url="$1" + out="$2" + hdr="$3" + ua="$4" + host="$5" + ip="$6" + if [ -n "$host" ] && [ -n "$ip" ]; then + curl -sSL --connect-timeout 15 --max-time 60 --speed-time 30 \ + --speed-limit 1 --retry 2 -A "$ua" -D "$hdr" -o "$out" \ + --resolve "$host:443:$ip" --resolve "$host:80:$ip" \ + -w '%{http_code}' "$url" 2>/dev/null + else + curl -sSL --connect-timeout 15 --max-time 60 --speed-time 30 \ + --speed-limit 1 --retry 2 -A "$ua" -D "$hdr" -o "$out" \ + -w '%{http_code}' "$url" 2>/dev/null + fi +} + +subscription_ua() { + local target ua + target="$1" + ua="" + [ -r "${target}.ua" ] && ua="$(sed -n '1p' "${target}.ua" 2>/dev/null)" + [ -n "$ua" ] || ua="$(uci -q get clashoo.config.sub_ua 2>/dev/null)" + [ -n "$ua" ] || ua="$DEFAULT_SUB_UA" + printf '%s' "$ua" +} + +save_subscription_ua() { + printf '%s\n' "$2" >"${1}.ua" +} + +curl_error() { + case "$1" in + 6) printf '%s' "DNS 解析失败" ;; + 7) printf '%s' "连接失败" ;; + 28) printf '%s' "连接超时" ;; + 35) printf '%s' "TLS 握手失败" ;; + 60) printf '%s' "证书校验失败" ;; + *) printf '网络错误(curl rc=%s)' "${1:-unknown}" ;; + esac +} + +download_to() { + local url out hdr ua code rc last_rc host dns ip proxy + url="$1" + out="$2" + hdr="$3" + ua="$4" + [ -n "$ua" ] || ua="$DEFAULT_SUB_UA" + DOWNLOAD_ERROR="" + + rm -f "$out" "$hdr" >/dev/null 2>&1 + if command -v curl >/dev/null 2>&1; then + code="$(curl_download "$url" "$out" "$hdr" "$ua" "" "")" + rc=$? + if [ "$rc" -eq 0 ] && [ "$code" = "200" ]; then + return 0 + fi + if [ "$rc" -eq 0 ] && [ -n "$code" ] && [ "$code" != "000" ]; then + DOWNLOAD_ERROR="HTTP ${code}" + return 1 + fi + last_rc="$rc" + host="$(extract_host "$url")" + for dns in 223.5.5.5 119.29.29.29 1.1.1.1 8.8.8.8; do + ip="$(resolve_via "$host" "$dns")" + [ -n "$ip" ] || continue + log_update "DNS 回退:${host} -> ${ip} (@${dns})" + code="$(curl_download "$url" "$out" "$hdr" "$ua" "$host" "$ip")" + rc=$? + [ "$rc" -eq 0 ] && [ "$code" = "200" ] && return 0 + if [ "$rc" -eq 0 ] && [ -n "$code" ] && [ "$code" != "000" ]; then + DOWNLOAD_ERROR="HTTP ${code}" + return 1 + fi + last_rc="$rc" + done + # Last resort: direct + DNS-override both failed, so the source may be + # GFW-blocked (e.g. a github-hosted sub). In kernel-only mode try once + # through the running core — nothing left to lose, and it won't disturb + # any airport that already answered direct. + proxy="$(clashoo_detect_proxy)" + if [ -n "$proxy" ]; then + log_update "代理兜底:通过本地核心重试订阅" + code="$(curl -sSL --connect-timeout 15 --max-time 60 --speed-time 30 \ + --speed-limit 1 --retry 1 -A "$ua" -D "$hdr" -o "$out" \ + --proxy "$proxy" -w '%{http_code}' "$url" 2>/dev/null)" + rc=$? + [ "$rc" -eq 0 ] && [ "$code" = "200" ] && return 0 + if [ "$rc" -eq 0 ] && [ -n "$code" ] && [ "$code" != "000" ]; then + DOWNLOAD_ERROR="HTTP ${code}" + return 1 + fi + last_rc="$rc" + fi + DOWNLOAD_ERROR="$(curl_error "$last_rc")" + return 1 + fi + wget -q --timeout=20 --user-agent="$ua" "$url" -O "$out" + rc=$? + [ "$rc" -eq 0 ] && return 0 + DOWNLOAD_ERROR="网络错误(wget rc=${rc})" + return 1 +} + +update_info() { + local hdr target info + hdr="$1" + target="$2" + info="$(grep -i 'subscription-userinfo:' "$hdr" 2>/dev/null | head -1 | \ + sed 's/^[Ss]ubscription-[Uu]serinfo:[[:space:]]*//' | tr -d '\r')" + if [ -n "$info" ]; then + printf '%s\n' "$info" >"${target}.info" + else + rm -f "${target}.info" >/dev/null 2>&1 + fi +} + +service_running() { + [ "$("$SERVICE_CMD" status 2>/dev/null)" = "running" ] +} + +template_output_name() { + local sub tpl + sub="$(printf '%s' "$1" | sed -e 's/\.[Yy][Aa][Mm][Ll]$//' -e 's/\.[Yy][Mm][Ll]$//' -e 's/[^A-Za-z0-9._-]/-/g')" + tpl="$(printf '%s' "$2" | sed -e 's/\.[Yy][Aa][Mm][Ll]$//' -e 's/\.[Yy][Mm][Ll]$//' -e 's/[^A-Za-z0-9._-]/-/g')" + printf '_merged_%s__%s.yaml' "${sub:-sub}" "${tpl:-template}" +} + +apply_template() { + local name target template merged merged_path use_config + name="$1" + target="$2" + [ -r "$BIND_FILE" ] || return 0 + [ -x /usr/share/clashoo/update/template_merge.sh ] || return 0 + template="$(awk -F '#' -v n="$name" '$1==n && ($3=="1" || $3=="true") {print $2; exit}' "$BIND_FILE")" + [ -n "$template" ] && [ -r "$TEMPLATE_DIR/$template" ] || return 0 + merged="$(template_output_name "$name" "$template")" + merged_path="$TEMPLATE_DIR/$merged" + if sh /usr/share/clashoo/update/template_merge.sh "$target" "$TEMPLATE_DIR/$template" "$merged_path" >/dev/null 2>&1; then + use_config="$(uci -q get clashoo.config.use_config 2>/dev/null)" + if [ "$use_config" = "$target" ] || [ "$use_config" = "$merged_path" ]; then + uci -q set clashoo.config.use_config="$merged_path" + uci -q set clashoo.config.config_type='3' + uci -q commit clashoo >/dev/null 2>&1 + restart_needed=1 + fi + else + log_update "模板生成失败:${name} <- ${template}" + fi +} + +update_mihomo() { + local name url typ target tmp hdr ua use_config config_type + name="$1" + url="$2" + typ="$3" + safe_name "$name" || return 1 + case "$typ" in clash|meta) ;; *) skipped=$((skipped + 1)); return 0 ;; esac + target="$SUB_DIR/$name" + [ -f "$target" ] || { skipped=$((skipped + 1)); return 0; } + tmp="$TMP_DIR/clashoo_sub_$$.yaml" + hdr="$TMP_DIR/clashoo_sub_$$.hdr" + ua="$(subscription_ua "$target")" + if ! download_to "$url" "$tmp" "$hdr" "$ua"; then + failed=$((failed + 1)) + log_update "更新失败(${DOWNLOAD_ERROR:-下载失败}):$name" + rm -f "$tmp" "$hdr" + return 1 + fi + if ! grep -Eq '^(proxies|proxy-providers):' "$tmp" 2>/dev/null; then + failed=$((failed + 1)) + log_update "更新失败(无效 Mihomo 配置):$name" + rm -f "$tmp" "$hdr" + return 1 + fi + if cmp -s "$tmp" "$target"; then + update_info "$hdr" "$target" + save_subscription_ua "$target" "$ua" + unchanged=$((unchanged + 1)) + rm -f "$tmp" "$hdr" + return 0 + fi + if ! mv "$tmp" "$target"; then + failed=$((failed + 1)) + rm -f "$hdr" + return 1 + fi + update_info "$hdr" "$target" + save_subscription_ua "$target" "$ua" + rm -f "$hdr" + updated=$((updated + 1)) + use_config="$(uci -q get clashoo.config.use_config 2>/dev/null)" + config_type="$(uci -q get clashoo.config.config_type 2>/dev/null)" + [ "$config_type" = "1" ] && [ "$use_config" = "$target" ] && restart_needed=1 + apply_template "$name" "$target" + log_update "更新完成:$name" +} + +valid_singbox() { + ucode -e 'import { readfile } from "fs"; let c = json(readfile(ARGV[0])); exit(type(c) == "object" && type(c.outbounds) == "array" && length(c.outbounds) > 0 ? 0 : 1);' "$1" >/dev/null 2>&1 +} + +update_singbox() { + local name target url tmp hdr ua active + name="$1" + safe_name "$name" || return 1 + target="$SINGBOX_DIR/$name" + [ -f "$target" ] && [ -r "$target.url" ] || { skipped=$((skipped + 1)); return 0; } + url="$(sed -n '1p' "$target.url")" + [ -n "$url" ] || { skipped=$((skipped + 1)); return 0; } + tmp="$TMP_DIR/clashoo_sb_$$.json" + hdr="$TMP_DIR/clashoo_sb_$$.hdr" + ua="$(subscription_ua "$target")" + if ! download_to "$url" "$tmp" "$hdr" "$ua"; then + failed=$((failed + 1)) + log_update "更新失败(${DOWNLOAD_ERROR:-下载失败}):$name" + rm -f "$tmp" "$hdr" + return 1 + fi + if ! valid_singbox "$tmp"; then + failed=$((failed + 1)) + log_update "更新失败(无效 sing-box 配置):$name" + rm -f "$tmp" "$hdr" + return 1 + fi + if cmp -s "$tmp" "$target"; then + update_info "$hdr" "$target" + save_subscription_ua "$target" "$ua" + unchanged=$((unchanged + 1)) + rm -f "$tmp" "$hdr" + return 0 + fi + if ! mv "$tmp" "$target"; then + failed=$((failed + 1)) + rm -f "$hdr" + return 1 + fi + update_info "$hdr" "$target" + save_subscription_ua "$target" "$ua" + rm -f "$hdr" + updated=$((updated + 1)) + active="$(uci -q get clashoo.config.singbox_active 2>/dev/null)" + [ "$active" = "$name" ] && restart_needed=1 + log_update "更新完成:$name" +} + +update_all() { + local name url typ url_file + if [ -r "$LIST_FILE" ]; then + while IFS='#' read -r name url typ _rest; do + [ -n "$name" ] && [ -n "$url" ] || continue + update_mihomo "$name" "$url" "$typ" || true + done <"$LIST_FILE" + fi + for url_file in "$SINGBOX_DIR"/*.url; do + [ -f "$url_file" ] || continue + name="$(basename "$url_file" .url)" + update_singbox "$name" || true + done +} + +case "$1" in + --all) + update_all + ;; + --mihomo) + line="$(awk -F '#' -v n="$2" '$1==n {print; exit}' "$LIST_FILE" 2>/dev/null)" + [ -n "$line" ] || { failed=1; message="subscription not found"; } + if [ -n "$line" ]; then + name="$(printf '%s' "$line" | awk -F '#' '{print $1}')" + url="$(printf '%s' "$line" | awk -F '#' '{print $2}')" + typ="$(printf '%s' "$line" | awk -F '#' '{print $3}')" + update_mihomo "$name" "$url" "$typ" || true + fi + ;; + --singbox) + update_singbox "$2" || true + ;; + *) + failed=1 + message="invalid arguments" + ;; +esac + +if [ "$restart_needed" = "1" ] && service_running; then + "$SERVICE_CMD" restart >/dev/null 2>&1 +fi + +finished_at="$(date +%s)" +[ -n "$message" ] || message="updated=$updated unchanged=$unchanged failed=$failed skipped=$skipped" +[ "$record_status" = "1" ] && write_status 0 "$finished_at" +[ "$failed" -eq 0 ] diff --git a/clashoo/files/usr/share/clashoo/update/subscription_update_cron.sh b/clashoo/files/usr/share/clashoo/update/subscription_update_cron.sh new file mode 100755 index 00000000..1f83307c --- /dev/null +++ b/clashoo/files/usr/share/clashoo/update/subscription_update_cron.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +STATUS_FILE="${CLASHOO_STATUS_FILE:-/usr/share/clashbackup/subscription_update.status}" +UPDATER="${CLASHOO_SUBSCRIPTION_UPDATER:-/usr/share/clashoo/update/subscription_update.sh}" +SERVICE_CMD="${CLASHOO_SERVICE_CMD:-/etc/init.d/clashoo}" +interval="$(uci -q get clashoo.config.subscription_update_interval 2>/dev/null)" +now="$(date +%s)" +last=0 + +[ "$(uci -q get clashoo.config.auto_subscription_update 2>/dev/null)" = "1" ] || exit 0 +"$SERVICE_CMD" status >/dev/null 2>&1 || exit 0 +echo "$interval" | grep -Eq '^[0-9]+$' || interval=72 +[ "$interval" -gt 0 ] || interval=72 +[ -r "$STATUS_FILE" ] && last="$(sed -n 's/^last_run=//p' "$STATUS_FILE" | head -1)" +echo "$last" | grep -Eq '^[0-9]+$' || last=0 + +[ $((now - last)) -ge $((interval * 3600)) ] || exit 0 +sh "$UPDATER" --all diff --git a/clashoo/files/usr/share/clashoo/update/template_merge.sh b/clashoo/files/usr/share/clashoo/update/template_merge.sh new file mode 100755 index 00000000..63be16c1 --- /dev/null +++ b/clashoo/files/usr/share/clashoo/update/template_merge.sh @@ -0,0 +1,101 @@ +#!/bin/sh + +set -eu + +SUB_FILE="${1:-}" +TEMPLATE_FILE="${2:-}" +OUT_FILE="${3:-}" +TMP_FILE="/tmp/clash_template_merge_$$.yaml" +TMP_FILE2="${TMP_FILE}.norm" +LOG_FILE="/tmp/clash_update.txt" + +log() { + printf ' %s - %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$1" >>"$LOG_FILE" +} + +cleanup() { + rm -f "$TMP_FILE" "$TMP_FILE2" >/dev/null 2>&1 || true +} +trap cleanup EXIT INT TERM + +[ -n "$SUB_FILE" ] || { log "模板复写失败:缺少订阅文件参数"; exit 1; } +[ -n "$TEMPLATE_FILE" ] || { log "模板复写失败:缺少模板文件参数"; exit 1; } +[ -n "$OUT_FILE" ] || { log "模板复写失败:缺少输出文件参数"; exit 1; } + +[ -f "$SUB_FILE" ] || { log "模板复写失败:订阅文件不存在"; exit 1; } +[ -f "$TEMPLATE_FILE" ] || { log "模板复写失败:模板文件不存在"; exit 1; } + +if ! command -v yq >/dev/null 2>&1; then + log "模板复写失败:缺少 yq" + exit 1 +fi + +core_bin="" +for b in mihomo clash-meta clash; do + if command -v "$b" >/dev/null 2>&1; then + core_bin="$b" + break + fi +done + +mkdir -p "$(dirname "$OUT_FILE")" >/dev/null 2>&1 + +log "开始模板复写:$(basename "$SUB_FILE") <- $(basename "$TEMPLATE_FILE")" +export SUB_FILE TEMPLATE_FILE + +# template is base; load() forces single-doc; explode anchors first (yq 4.53+ breaks "<<") +yq -n ' + (load(strenv(SUB_FILE)) | explode(.)) as $sub | + (load(strenv(TEMPLATE_FILE)) | explode(.)) as $tpl | + ( + ($tpl * { + "proxies": (((($tpl.proxies // []) + ($sub.proxies // [])) | unique_by(.name))), + "proxy-providers": (($tpl."proxy-providers" // {}) * ($sub."proxy-providers" // {})) + }) + | .dns = ((.dns // {}) * { + "proxy-server-nameserver": ( + .dns."proxy-server-nameserver" + // $sub.dns."proxy-server-nameserver" + // $sub.dns.nameserver + // ["223.5.5.5", "119.29.29.29"] + ) + }) + ) +' >"$TMP_FILE" 2>/dev/null || { + log "模板复写失败:YAML 合并错误" + exit 1 +} + +# 兼容第三方模板中使用“直连”字样,统一转换为 Clash 内置 DIRECT。 +yq e ' + (.. | select(tag == "!!str")) |= sub("^直连$", "DIRECT") + | .rules = ((.rules // []) | map(sub(",直连,", ",DIRECT,") | sub(",直连$", ",DIRECT"))) +' "$TMP_FILE" >"$TMP_FILE2" 2>/dev/null || { + log "模板复写失败:直连别名标准化失败" + exit 1 +} +mv -f "$TMP_FILE2" "$TMP_FILE" >/dev/null 2>&1 || { + log "模板复写失败:中间文件写入失败" + exit 1 +} + +yq e '.' "$TMP_FILE" >/dev/null 2>&1 || { + log "模板复写失败:合并结果 YAML 无效" + exit 1 +} + +if [ -n "$core_bin" ]; then + "$core_bin" -t -f "$TMP_FILE" >/dev/null 2>&1 || { + log "模板复写失败:内核校验不通过" + exit 1 + } +fi + +mv -f "$TMP_FILE" "$OUT_FILE" >/dev/null 2>&1 || { + log "模板复写失败:写入输出文件失败" + exit 1 +} + +chmod 644 "$OUT_FILE" >/dev/null 2>&1 || true +log "模板复写完成:$(basename "$OUT_FILE")" +exit 0 diff --git a/clashoo/files/usr/share/clashoo/update/update.sh b/clashoo/files/usr/share/clashoo/update/update.sh new file mode 100755 index 00000000..0b0a8936 --- /dev/null +++ b/clashoo/files/usr/share/clashoo/update/update.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +config_name="${1:-$(uci -q get clashoo.config.config_update_name 2>/dev/null)}" +[ -n "$config_name" ] || exit 1 +exec sh /usr/share/clashoo/update/subscription_update.sh --mihomo "$config_name" diff --git a/clashoo/files/usr/share/clashoo/update/update_all.sh b/clashoo/files/usr/share/clashoo/update/update_all.sh new file mode 100755 index 00000000..b743529d --- /dev/null +++ b/clashoo/files/usr/share/clashoo/update/update_all.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +UPDATE_LOG="/tmp/clash_update.txt" +STATE_DIR="${CLASHOO_STATE_DIR:-/usr/share/clashbackup}" + +log_update() { + printf ' %s - %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$1" >>"$UPDATE_LOG" +} + +log_update "更新大陆白名单" +sh /usr/share/clashoo/update/update_china_ip.sh >> "$UPDATE_LOG" 2>&1 + +log_update "更新 GeoIP / GeoSite" +sh /usr/share/clashoo/update/geoip.sh >/dev/null 2>&1 + +mkdir -p "$STATE_DIR" +tmp="$STATE_DIR/rule_update.status.tmp.$$" +printf 'last_run=%s\n' "$(date +%s)" >"$tmp" && mv -f "$tmp" "$STATE_DIR/rule_update.status" + +exit 0 diff --git a/clashoo/files/usr/share/clashoo/update/update_china_ip.sh b/clashoo/files/usr/share/clashoo/update/update_china_ip.sh new file mode 100755 index 00000000..8d31b73d --- /dev/null +++ b/clashoo/files/usr/share/clashoo/update/update_china_ip.sh @@ -0,0 +1,237 @@ +#!/bin/sh + +set -eu + +LOG_FILE="${LOG_FILE:-/tmp/clash_update.txt}" +NFT_DIR="${NFT_DIR:-/usr/share/clashoo/nftables}" +TARGET_V4="${NFT_DIR}/geoip_cn.nft" +TARGET_V6="${NFT_DIR}/geoip6_cn.nft" +FW4_SCRIPT="${FW4_SCRIPT:-/usr/share/clashoo/net/fw4.sh}" +PROC_ROOT="${PROC_ROOT:-/proc}" +SINGBOX_BIN_DIR="${SINGBOX_BIN_DIR:-/usr/share/clashoo/bin}" +TMP_ROOT="$(mktemp -d /tmp/clashoo_china_ip.XXXXXX)" +TMP_V4="$TMP_ROOT/china_ip.txt" +TMP_V6="$TMP_ROOT/china_ipv6.txt" +OUT_V4="$TMP_ROOT/geoip_cn.nft" +OUT_V6="$TMP_ROOT/geoip6_cn.nft" + +log() { + printf ' %s - %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$1" >> "$LOG_FILE" +} + +bool_enabled() { + case "$1" in + 1|true|TRUE|yes|on) return 0 ;; + *) return 1 ;; + esac +} + +download_with_fallback() { + local url="$1" + local output="$2" + local ip + + if curl -fsSL --connect-timeout 10 --max-time 120 "$url" -o "$output"; then + return 0 + fi + + case "$url" in + https://ispip.clang.cn/*) + for ip in 182.247.248.127 103.220.64.183; do + if curl -fsSL --connect-timeout 10 --max-time 120 --resolve "ispip.clang.cn:443:${ip}" "$url" -o "$output"; then + log "DNS 异常,已使用 --resolve(${ip}) 回源下载" + return 0 + fi + done + ;; + esac + + return 1 +} + +cleanup() { + rm -rf "$TMP_ROOT" +} + +trap cleanup EXIT INT TERM + +render_nft_set() { + local source_file="$1" + local output_file="$2" + local set_name="$3" + local set_type="$4" + + awk -v set_name="$set_name" -v set_type="$set_type" ' + function valid_cidr(value, parts, octets, i, prefix) { + if (split(value, parts, "/") != 2 || parts[2] !~ /^[0-9]+$/) + return 0 + prefix = parts[2] + 0 + if (set_type == "ipv4_addr") { + if (prefix > 32 || split(parts[1], octets, ".") != 4) + return 0 + for (i = 1; i <= 4; i++) + if (octets[i] !~ /^[0-9]+$/ || length(octets[i]) > 3 || octets[i] + 0 > 255) + return 0 + return 1 + } + return set_type == "ipv6_addr" && prefix <= 128 && + parts[1] ~ /^[0-9A-Fa-f:]+$/ && index(parts[1], ":") > 0 + } + BEGIN { + print "set " set_name " {" + print "\ttype " set_type ";" + print "\tflags interval;" + print "\tauto-merge;" + print "\telements = {" + first = 1 + } + !/^[[:space:]]*$/ && !/^[[:space:]]*#/ { + gsub(/^[[:space:]]+|[[:space:]]+$/, "", $0) + if ($0 == "") + next + if (!valid_cidr($0)) { + if (!invalid) + printf "invalid CIDR at line %d\n", NR > "/dev/stderr" + invalid = 1 + next + } + if (!first) + print "," + printf "\t\t%s", $0 + first = 0 + } + END { + if (!first) + print "" + print "\t}" + print "}" + if (invalid || first) + exit 1 + } + ' "$source_file" > "$output_file" || return 1 + # nft catches malformed IPv6 forms and invalid network prefixes before replacement. + { printf 'table inet clashoo_validate {\n'; cat "$output_file"; printf '\n}\n'; } | + nft -c -f - >/dev/null 2>&1 +} + +url4="$(uci -q get clashoo.config.china_ip_url 2>/dev/null || true)" +url6="$(uci -q get clashoo.config.china_ipv6_url 2>/dev/null || true)" +bypass_china="$(uci -q get clashoo.config.bypass_china 2>/dev/null || true)" +bypass_china_ipv6="$(uci -q get clashoo.config.bypass_china_ipv6 2>/dev/null || true)" +[ -n "$bypass_china_ipv6" ] || bypass_china_ipv6="$bypass_china" + +[ -n "$url4" ] || url4='https://ispip.clang.cn/all_cn.txt' +[ -n "$url6" ] || url6='https://ispip.clang.cn/all_cn_ipv6.txt' + +mkdir -p "$NFT_DIR" + +log '开始更新大陆白名单' + +download_with_fallback "$url4" "$TMP_V4" +[ -s "$TMP_V4" ] || { + log '大陆 IPv4 白名单下载失败:返回为空' + exit 1 +} +changed=0 + +if ! render_nft_set "$TMP_V4" "$OUT_V4" clashoo_china ipv4_addr; then + log '大陆 IPv4 白名单包含无效 CIDR,保留原文件' + exit 1 +fi +if cmp -s "$OUT_V4" "$TARGET_V4"; then + log '大陆 IPv4 白名单内容无变化' +else + mv "$OUT_V4" "$TARGET_V4" + chmod 644 "$TARGET_V4" >/dev/null 2>&1 || true + changed=1 + log '大陆 IPv4 白名单更新完成' +fi + +if download_with_fallback "$url6" "$TMP_V6"; then + if [ -s "$TMP_V6" ]; then + if ! render_nft_set "$TMP_V6" "$OUT_V6" clashoo_china6 ipv6_addr; then + log '大陆 IPv6 白名单包含无效 CIDR,保留原文件' + exit 1 + fi + if cmp -s "$OUT_V6" "$TARGET_V6"; then + log '大陆 IPv6 白名单内容无变化' + else + mv "$OUT_V6" "$TARGET_V6" + chmod 600 "$TARGET_V6" >/dev/null 2>&1 || true + changed=1 + log '大陆 IPv6 白名单更新完成' + fi + else + log '大陆 IPv6 白名单下载为空,保留原文件' + fi +else + log '大陆 IPv6 白名单下载失败,保留原文件' +fi + +instance_pid() { + local service="$1" + local instance="${2:-}" + local filter + if [ -n "$instance" ]; then + filter="@.${service}.instances.${instance}.pid" + else + filter="@[\"${service}\"].instances.*.pid" + fi + ubus call service list "{\"name\":\"$service\",\"verbose\":true}" 2>/dev/null | + jsonfilter -e "$filter" 2>/dev/null | head -n1 +} + +pid_running() { + [ -n "$1" ] && [ -d "$PROC_ROOT/$1" ] +} + +singbox_pid_owns_private_binary() { + local pid="$1" exe cmdline + pid_running "$pid" || return 1 + exe="$(readlink -f "$PROC_ROOT/$pid/exe" 2>/dev/null)" + case "$exe" in + "$SINGBOX_BIN_DIR/sing-box-stable"|"$SINGBOX_BIN_DIR/sing-box-alpha") return 0 ;; + esac + cmdline="$(tr '\000' ' ' < "$PROC_ROOT/$pid/cmdline" 2>/dev/null)" + case "$cmdline" in + "$SINGBOX_BIN_DIR/sing-box-stable"|"$SINGBOX_BIN_DIR/sing-box-stable "*|"$SINGBOX_BIN_DIR/sing-box-alpha"|"$SINGBOX_BIN_DIR/sing-box-alpha "*) return 0 ;; + esac + return 1 +} + +selected_core_running() { + local name pid + if [ "$(uci -q get clashoo.config.core_type 2>/dev/null)" = "singbox" ]; then + name="clashoo" + pid="$(instance_pid "$name" singbox)" + singbox_pid_owns_private_binary "$pid" + return $? + else + name="clashoo" + fi + pid="$(instance_pid "$name")" + pid_running "$pid" +} + +firewall_managed() { + bool_enabled "$(uci -q get clashoo.config.enable 2>/dev/null)" || return 1 + [ "$(uci -q get clashoo.config.core_only 2>/dev/null)" = "1" ] && return 1 + selected_core_running +} + +if [ "$changed" -eq 0 ]; then + log '白名单内容未变,跳过防火墙重载' +elif ! firewall_managed; then + log '未接管防火墙(服务未运行或仅内核模式),仅更新白名单文件' +elif bool_enabled "$bypass_china" || bool_enabled "$bypass_china_ipv6"; then + if "$FW4_SCRIPT" apply >/dev/null 2>&1; then + log "大陆白名单规则已重载(IPv4=${bypass_china:-0},IPv6=${bypass_china_ipv6:-0})" + else + log '大陆白名单规则重载失败' + exit 1 + fi +else + log '大陆 IPv4/IPv6 绕过均未启用,仅更新白名单文件' +fi + +log '大陆白名单更新流程完成' diff --git a/clashoo/patches/001-skip-invalid-fakeip-domains.patch b/clashoo/patches/001-skip-invalid-fakeip-domains.patch new file mode 100644 index 00000000..9e3b042e --- /dev/null +++ b/clashoo/patches/001-skip-invalid-fakeip-domains.patch @@ -0,0 +1,91 @@ +--- a/dns/middleware.go ++++ b/dns/middleware.go +@@ -12,6 +12,7 @@ import ( + icontext "github.com/metacubex/mihomo/context" + "github.com/metacubex/mihomo/log" + ++ M "github.com/metacubex/sing/common/metadata" + D "github.com/miekg/dns" + ) + +@@ -152,6 +153,9 @@ func withFakeIP(skipper *fakeip.Skipper, + q := r.Question[0] + + host := strings.TrimRight(q.Name, ".") ++ if !M.IsDomainName(host) { ++ return next(ctx, r) ++ } + if skipper.ShouldSkipped(host) { + return next(ctx, r) + } +--- /dev/null ++++ b/dns/middleware_test.go +@@ -0,0 +1,68 @@ ++package dns ++ ++import ( ++ "context" ++ "net/netip" ++ "testing" ++ ++ "github.com/metacubex/mihomo/component/fakeip" ++ icontext "github.com/metacubex/mihomo/context" ++ ++ D "github.com/miekg/dns" ++ "github.com/stretchr/testify/require" ++) ++ ++func TestWithFakeIPSkipsInvalidDomain(t *testing.T) { ++ pool, err := fakeip.New(fakeip.Options{ ++ IPNet: netip.MustParsePrefix("198.18.0.1/16"), ++ Size: 10, ++ }) ++ require.NoError(t, err) ++ ++ downstreamCalled := false ++ next := func(_ *icontext.DNSContext, request *D.Msg) (*D.Msg, error) { ++ downstreamCalled = true ++ response := new(D.Msg) ++ response.SetReply(request) ++ return response, nil ++ } ++ ++ request := new(D.Msg) ++ request.SetQuestion("n-relay-ipc-txc-nj-00.tplinkcloud.com.cn\\152.", D.TypeA) ++ response, err := withFakeIP(&fakeip.Skipper{}, pool, nil, 1)(next)(icontext.NewDNSContext(context.Background()), request) ++ ++ require.NoError(t, err) ++ require.True(t, downstreamCalled) ++ require.Empty(t, response.Answer) ++ _, mapped := pool.LookBack(netip.MustParseAddr("198.18.0.4")) ++ require.False(t, mapped) ++} ++ ++func TestWithFakeIPKeepsValidDomain(t *testing.T) { ++ pool, err := fakeip.New(fakeip.Options{ ++ IPNet: netip.MustParsePrefix("198.18.0.1/16"), ++ Size: 10, ++ }) ++ require.NoError(t, err) ++ ++ downstreamCalled := false ++ next := func(_ *icontext.DNSContext, request *D.Msg) (*D.Msg, error) { ++ downstreamCalled = true ++ response := new(D.Msg) ++ response.SetReply(request) ++ return response, nil ++ } ++ ++ request := new(D.Msg) ++ request.SetQuestion("n-relay-ipc-txc-nj-00.tplinkcloud.com.cn.", D.TypeA) ++ dnsCtx := icontext.NewDNSContext(context.Background()) ++ response, err := withFakeIP(&fakeip.Skipper{}, pool, nil, 1)(next)(dnsCtx, request) ++ ++ require.NoError(t, err) ++ require.False(t, downstreamCalled) ++ require.Len(t, response.Answer, 1) ++ require.Equal(t, icontext.DNSTypeFakeIP, dnsCtx.Type()) ++ host, mapped := pool.LookBack(netip.MustParseAddr("198.18.0.4")) ++ require.True(t, mapped) ++ require.Equal(t, "n-relay-ipc-txc-nj-00.tplinkcloud.com.cn", host) ++} diff --git a/ddns-go/Makefile b/ddns-go/Makefile index 173aa1e2..9d7ccb7b 100644 --- a/ddns-go/Makefile +++ b/ddns-go/Makefile @@ -1,45 +1,45 @@ # SPDX-License-Identifier: GPL-3.0-only # # Copyright (C) 2021-2026 sirpdboy -# +# include $(TOPDIR)/rules.mk PKG_NAME:=ddns-go -PKG_VERSION:=6.17.1 +PKG_VERSION:=6.17.7 PKG_RELEASE:=1 - +PKG_VERSION:=6.17.7 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE_URL:=https://codeload.github.com/jeessy2/ddns-go/tar.gz/v$(PKG_VERSION)? -PKG_HASH:=05ae7ef07241113ee2cd861be530524f06856fc7368b84846db5137a477277ea +PKG_HASH:=f7001004e092d9641aad5a94158e0b4cae4a53a7f5c7d96d5c6af3d246c56fcc -PKG_LICENSE:=MIT +PKG_HASH:=f7001004e092d9641aad5a94158e0b4cae4a53a7f5c7d96d5c6af3d246c56fcc PKG_LICENSE_FILES:=LICENSE -PKG_MAINTAINER:=sirpdboy +PKG_MAINTAINER:=Tianling Shen PKG_BUILD_DEPENDS:=golang/host PKG_BUILD_PARALLEL:=1 PKG_BUILD_FLAGS:=no-mips16 GO_PKG:=github.com/jeessy2/ddns-go/v6 -GO_PKG_LDFLAGS_X:=main.Version=$(PKG_VERSION) -GO_PKG_LDFLAGS:=$(GO_PKG_LDFLAGS_X) +GO_PKG_LDFLAGS_X:=main.version=$(PKG_VERSION) include $(INCLUDE_DIR)/package.mk include $(TOPDIR)/feeds/packages/lang/golang/golang-package.mk define Package/ddns-go + TITLE:=A Linux web GUI client of ddns-go SECTION:=net CATEGORY:=Network - SUBMENU:=IP Addresses and Names - TITLE:=Support IPV4 and IPV6 simple and easy-to-use IP binding domain name tool - URL:=https://github.com/jeessy2/ddns-go + SUBMENU:=Web Servers/Proxies DEPENDS:=$(GO_ARCH_DEPENDS) +ca-bundle + URL:=https://github.com/jeessy2/ddns-go USERID:=ddns-go:ddns-go endef define Package/ddns-go/description - A simple and easy-to-use IP binding domain name tool that supports IPV4 and IPV6. + ddns-go is a automatically obtain your public IPv4 or IPv6 address and resolve it to the corresponding domain name service, + support Alidns Dnspod Cloudflare Hicloud Callback Baiducloud porkbun GoDaddy Google Domains. endef define Package/ddns-go/conffiles @@ -47,10 +47,9 @@ define Package/ddns-go/conffiles /etc/ddns-go/ddns-go-config.yaml endef - define Package/ddns-go/install $(call GoPackage/Package/Install/Bin,$(1)) - + $(INSTALL_DIR) $(1)/etc/init.d $(INSTALL_DIR) $(1)/etc/config $(INSTALL_BIN) $(CURDIR)/files/ddns-go.init $(1)/etc/init.d/ddns-go diff --git a/ddns-go/files/ddns-go.conf b/ddns-go/files/ddns-go.conf index e13a09f6..89b43d80 100644 --- a/ddns-go/files/ddns-go.conf +++ b/ddns-go/files/ddns-go.conf @@ -1,9 +1,9 @@ config basic 'config' - option enabled '0' - option logger '1' - option port '9876' - option time '300' - option ctimes '5' - option skipverify '0' - option delay '0' - option dns '223.5.5.5' + option enabled '0' + option logger '1' + option port '9876' + option time '300' + option ctimes '5' + option skipverify '0' + option delay '0' + option dns '223.5.5.5' diff --git a/ddns-go/files/ddns-go.init b/ddns-go/files/ddns-go.init old mode 100644 new mode 100755 index c478656e..d9389b6e --- a/ddns-go/files/ddns-go.init +++ b/ddns-go/files/ddns-go.init @@ -1,6 +1,6 @@ #!/bin/sh /etc/rc.common # -# Copyright (C) 2021-2026 sirpdboy +# Copyright (C) 2021-2026 sirpdboy # # This file is part of ddns-go . # @@ -15,70 +15,71 @@ CONFDIR=/etc/ddns-go CONF=$CONFDIR/ddns-go-config.yaml init_yaml() { - [ -d "$CONFDIR" ] || mkdir -p "$CONFDIR" - chown -R ddns-go:ddns-go "$CONFDIR" - chmod 755 "$CONFDIR" - [ -f "$CONF" ] && chmod 644 "$CONF" + [ -d "$CONFDIR" ] || mkdir -p "$CONFDIR" + chown -R ddns-go:ddns-go "$CONFDIR" + chmod 755 "$CONFDIR" + [ -f "$CONF" ] && chmod 644 "$CONF" } build_args() { - local cfg="$1" - local args="-c $CONF" - - config_get port "$cfg" port '9876' - args="$args -l :$port" - - config_get time "$cfg" time '300' - [ -n "$time" ] && args="$args -f $time" - - config_get ctimes "$cfg" ctimes '5' - [ -n "$ctimes" ] && args="$args -cacheTimes $ctimes" - - config_get dns "$cfg" dns '223.5.5.5' - [ -n "$dns" ] && args="$args -dns $dns" - - config_get_bool noweb "$cfg" noweb 0 - [ "$noweb" -eq 1 ] && args="$args -noweb" - - config_get_bool skipverify "$cfg" skipverify 0 - [ "$skipverify" -eq 1 ] && args="$args -skipVerify" - - echo "$args" + local cfg="$1" + local args="-c $CONF" + + config_get port "$cfg" port '9876' + args="$args -l :$port" + + config_get time "$cfg" time '300' + [ -n "$time" ] && args="$args -f $time" + + config_get ctimes "$cfg" ctimes '5' + [ -n "$ctimes" ] && args="$args -cacheTimes $ctimes" + + config_get dns "$cfg" dns '223.5.5.5' + [ -n "$dns" ] && args="$args -dns $dns" + + config_get_bool noweb "$cfg" noweb 0 + [ "$noweb" -eq 1 ] && args="$args -noweb" + + config_get_bool skipverify "$cfg" skipverify 0 + [ "$skipverify" -eq 1 ] && args="$args -skipVerify" + + echo "$args" } start_instance() { - local cfg="$1" - config_get_bool enabled "$cfg" enabled 0 - [ "$enabled" -eq 0 ] && return 0 - - config_get delay "$cfg" delay 0 - if [ "$delay" -gt 0 ]; then - local uptime=$(awk -F. '{print $1}' /proc/uptime) - [ "$uptime" -lt 120 ] && sleep "$delay" - fi - - init_yaml - - local args=$(build_args "$cfg") - - procd_open_instance - procd_set_param command $PROG $args + local cfg="$1" + local logger + config_get_bool enabled "$cfg" enabled 0 + [ "$enabled" -eq 0 ] && return 0 - procd_set_param stdout 1 - procd_set_param stderr 1 - procd_set_param log_level 7 - - procd_set_param user ddns-go - procd_set_param respawn - - procd_close_instance + config_get delay "$cfg" delay 0 + if [ "$delay" -gt 0 ]; then + local uptime=$(awk -F. '{print $1}' /proc/uptime) + [ "$uptime" -lt 120 ] && sleep "$delay" + fi + + init_yaml + + local args=$(build_args "$cfg") + + procd_open_instance + procd_set_param command $PROG $args + + config_get_bool logger "$cfg" logger 1 + procd_set_param stdout "$logger" + procd_set_param stderr "$logger" + + procd_set_param user ddns-go + procd_set_param respawn + + procd_close_instance } start_service() { - config_load "$NAME" - config_foreach start_instance 'basic' + config_load "$NAME" + config_foreach start_instance 'basic' } service_triggers() { - procd_add_reload_trigger "$NAME" -} \ No newline at end of file + procd_add_reload_trigger "$NAME" +} diff --git a/luci-app-chatgpt-web/.github/workflows/main.yml b/luci-app-chatgpt-web/.github/workflows/main.yml new file mode 100644 index 00000000..3de4bc6d --- /dev/null +++ b/luci-app-chatgpt-web/.github/workflows/main.yml @@ -0,0 +1,49 @@ +name: Build + +on: + push: + tags: + - v* + +jobs: + build: + name: Build ${{ matrix.arch }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + arch: + + - aarch64_generic + - i386_pentium-mmx + - loongarch64_generic + - mips_mips32 + - mipsel_24kc + - mipsel_mips32 + - x86_64 + + steps: + - uses: actions/checkout@main + with: + fetch-depth: 0 + - name: Building packages + uses: sbwml/openwrt-gh-action-sdk@go1.24 + env: + ARCH: ${{ matrix.arch }}-openwrt-24.10 + FEEDNAME: packages_ci + PACKAGES: luci-app-chatgpt-web + NO_REFRESH_CHECK: true + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.arch }} + path: bin/packages/${{ matrix.arch }}/packages_ci/*.ipk + + - name: Upload packages + uses: ncipollo/release-action@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + allowUpdates: true + replacesArtifacts: true + artifacts: "bin/packages/${{ matrix.arch }}/packages_ci/*.ipk" diff --git a/luci-app-chatgpt-web/Makefile b/luci-app-chatgpt-web/Makefile new file mode 100644 index 00000000..b5cae97b --- /dev/null +++ b/luci-app-chatgpt-web/Makefile @@ -0,0 +1,22 @@ +# SPDX-License-Identifier: GPL-3.0-only +# +# Copyright (C) 2023-2025 sirpdboy https://github.com/sirpdboy/luci-app-chatgpt-web +# +# This is free software, licensed under the Apache License, Version 2.0 . +# +include $(TOPDIR)/rules.mk + +NAME:=chatgpt-web +PKG_NAME:=luci-app-$(NAME) +PKG_VERSION:=1.2.1 +PKG_RELEASE:=16 + +LUCI_TITLE:=LuCI Support for chatgpt-web Client +LUCI_PKGARCH:=all + +define Package/$(PKG_NAME)/conffiles +/etc/config/chatgpt-web +endef +include $(TOPDIR)/feeds/luci/luci.mk + +# call BuildPackage - OpenWrt buildroot signature diff --git a/luci-app-chatgpt-web/README.md b/luci-app-chatgpt-web/README.md new file mode 100644 index 00000000..ab5de559 --- /dev/null +++ b/luci-app-chatgpt-web/README.md @@ -0,0 +1,179 @@ +## 访问数:![hello](https://views.whatilearened.today/views/github/sirpdboy/deplives.svg)[![](https://img.shields.io/badge/TG群-点击加入-FFFFFF.svg)](https://t.me/joinchat/AAAAAEpRF88NfOK5vBXGBQ) + + + +

+
luci-app-chatgpt-web
+

+ +

+OpenWrt +Chrome +Safari +Firefox + GitHub release +Downloads +

+ + +![screenshots](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/说明1.jpg) + +请 **认真阅读完毕** 本页面,本页面包含注意事项和如何使用。 + +## 功能说明 + +luci-app-chatgpt Pure ChatGPT by OPENWRT based on OpenAI API + +luci-app-chatgpt 在OPENWRT上实现ChatGPT项目,基于OpenAI API + + + +- [luci-app-chatgpt](#功能说明) + - [特性](#特性) + - [编译方法](#编译方法) + - [使用方法](#使用方法) + - [自定义选项](#自定义选项)。 + - [说明](#说明) + - [界面](#界面) + - [捐助](#捐助) + + + +## 特性 + +- 无须第三依赖,直接使用官方API接口。 +- 支持复制/更新/刷新会话,语音输入,朗读等功能,以及众多[自定义选项](#自定义选项)。 +- 支持搜索会话,导入导出会话和设置,自定义头像,自定义系统角色等。 + +参考项目: +[markdown-it](https://github.com/markdown-it/markdown-it), +[highlight.js](https://github.com/highlightjs/highlight.js), +[github-markdown-css](https://github.com/sindresorhus/github-markdown-css), +[chatgpt-html](https://github.com/slippersheepig/chatgpt-html), +[markdown-it-copy](https://github.com/ReAlign/markdown-it-copy), +[markdown-it-texmath](https://github.com/goessner/markdown-it-texmath), +[awesome-chatgpt-prompts-zh](https://github.com/PlexPt/awesome-chatgpt-prompts-zh) + + +## 编译方法 + +- 将luci-app-chatgpt添加至 LEDE/OpenWRT 源码的方法。 + +### 下载源码方法: + + ```Brach + + # 下载源码 + + git clone https://github.com/sirpdboy/chatgpt-web.git package/luci-app-chatgpt + make menuconfig + + ``` +### 配置菜单 + + ```Brach + make menuconfig + # 找到 LuCI -> Applications, 选择 luci-app-chatgpt, 保存后退出。 + ``` + +### 编译 + + ```Brach + # 编译固件 + make package/luci-app-chatgpt/compile V=s + ``` + +## 使用方法 + + - 打开luci-app-chatgpt基本设置,填写API密钥、GPT模型、填写OpenAI接口、选择用户图像等。 + + - 可选GPT模型,默认gpt-3.5,当前使用gpt-4模型需通过openai的表单申请。 + + - OpenAI接口当本地可正常访问`api.openai.com`,填写`https://api.openai.com/` + + - 不可正常访问`api.openai.com`,填写其反代地址,注意:反代接口响应需添加跨域Header `Access-Control-Allow-Origin` + + - chatgpt-web页面就可以正常使用了,如果需要设置更多请看 自定义设置设置语音和系统角色等。 + +## 自定义选项 + +- 左边栏支持,清除对话,搜索会话,新建/重命名/删除(会话/文件夹)导出/导入/重置会话和设置数据,显示本地存储。 + +- 可选系统角色,默认不开启,有四个预设角色,后期会加入更多角色。 + +- 可选角色性格,默认灵活创新,对应接口文档的top_p参数。 + +- 可选回答质量,默认平衡,对应接口文档的temperature参数。 + +- 修改打字机速度,默认较快,值越大速度越快。 + +- 允许连续对话,默认开启,对话中包含上下文信息,会导致api费用增加。 + +- 允许长回复,默认关闭,**开启后可能导致api费用增加,并丢失大部分上下文,对于一些要发送`继续`才完整的回复,不用发`继续`了。** + +- 选择语音,默认Bing语音,支持Azure语音和系统语音,可分开设置提问语音和回答语音。 + +- 音量,默认最大。 + +- 语速,默认正常。 + +- 音调,默认正常。 + +- 允许连续朗读,默认开启,连续郎读到所有对话结束。 + +- 允许自动朗读,默认关闭,自动朗读新的回答。**(iOS需打开设置-自动播放视频预览,Mac上Safari需打开此网站的设置-允许全部自动播放)** + +- 支持语音输入,默认识别为普通话,可长按语音按钮修改识别选项。**语音识别必需条件:使用chrome内核系浏览器 + https网页或本地网页。** 如点击语音按钮没反应,可能是未授予麦克风权限或者没安装麦克风设备。 + + +## 说明 + +-源码来源:https://github.com/sirpdboy/chatgpt-web +-你可以随意使用其中的源码,但请注明出处。 + +![screenshots](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/说明2.jpg) + +## 界面 + +![screenshots](./doc/chatgpt1.png) + +![screenshots](./doc/chatgpt2.png) + +![screenshots](./doc/chatgpt3.png) + + +## 使用与授权相关说明 + +- 本人开源的所有源码,任何引用需注明本处出处,如需修改二次发布必告之本人,未经许可不得做于任何商用用途。 + +# My other project + +- 路由安全看门狗 :https://github.com/sirpdboy/luci-app-watchdog +- 网络速度测试 :https://github.com/sirpdboy/luci-app-netspeedtest +- 计划任务插件(原定时设置) : https://github.com/sirpdboy/luci-app-taskplan +- 关机功能插件 : https://github.com/sirpdboy/luci-app-poweroffdevice +- opentopd主题 : https://github.com/sirpdboy/luci-theme-opentopd +- kucat酷猫主题: https://github.com/sirpdboy/luci-theme-kucat +- kucat酷猫主题设置工具: https://github.com/sirpdboy/luci-app-kucat-config +- NFT版上网时间控制插件: https://github.com/sirpdboy/luci-app-timecontrol +- 家长控制: https://github.com/sirpdboy/luci-theme-parentcontrol +- 定时限速: https://github.com/sirpdboy/luci-app-eqosplus +- 系统高级设置 : https://github.com/sirpdboy/luci-app-advanced +- ddns-go动态域名: https://github.com/sirpdboy/luci-app-ddns-go +- 进阶设置(系统高级设置+主题设置kucat/agron/opentopd): https://github.com/sirpdboy/luci-app-advancedplus +- 网络设置向导: https://github.com/sirpdboy/luci-app-netwizard +- 一键分区扩容: https://github.com/sirpdboy/luci-app-partexp +- lukcy大吉: https://github.com/sirpdboy/luci-app-lukcy + +## 捐助 + +![screenshots](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/说明3.jpg) + +| 图飞了😂 | 图飞了😂 | +| :-----------------: | :-------------: | +|![xm1](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/支付宝.png) | ![xm1](https://raw.githubusercontent.com/sirpdboy/openwrt/master/doc/微信.png) | + + + 图飞了😂 + + diff --git a/luci-app-chatgpt-web/doc/chatgpt1.png b/luci-app-chatgpt-web/doc/chatgpt1.png new file mode 100644 index 00000000..f715b006 Binary files /dev/null and b/luci-app-chatgpt-web/doc/chatgpt1.png differ diff --git a/luci-app-chatgpt-web/doc/chatgpt2.png b/luci-app-chatgpt-web/doc/chatgpt2.png new file mode 100644 index 00000000..7de824a2 Binary files /dev/null and b/luci-app-chatgpt-web/doc/chatgpt2.png differ diff --git a/luci-app-chatgpt-web/doc/chatgpt3.png b/luci-app-chatgpt-web/doc/chatgpt3.png new file mode 100644 index 00000000..9d280941 Binary files /dev/null and b/luci-app-chatgpt-web/doc/chatgpt3.png differ diff --git a/luci-app-chatgpt-web/luasrc/controller/chatgpt-web.lua b/luci-app-chatgpt-web/luasrc/controller/chatgpt-web.lua new file mode 100644 index 00000000..c100132c --- /dev/null +++ b/luci-app-chatgpt-web/luasrc/controller/chatgpt-web.lua @@ -0,0 +1,17 @@ +-- Copyright (C) 2023-2025 sirpdboy https://github.com/sirpdboy/luci-app-chatgpt-web +-- Licensed to the public under the Apache License 2.0. + +module("luci.controller.chatgpt-web", package.seeall) + +function index() + if not nixio.fs.access("/etc/config/chatgpt-web") then + return + end + + local e = entry({"admin", "services", "chatgpt-web"}, alias("admin", "services", "chatgpt-web", "setting"),_("Chatgpt Web"), 20) + e.dependent=false + e.acl_depends = { "luci-app-chatgpt-web" } + entry({"admin", "services", "chatgpt-web", "setting"}, cbi("chatgpt-web"), _("Base Setting"), 10).leaf=true + entry({"admin", "services", "chatgpt-web", "chatgpt-web"}, cbi("chatgpt",{hideapplybtn=true, hidesavebtn=true, hideresetbtn=true}), _("Chatgpt Web"), 30).leaf=true +end + diff --git a/luci-app-chatgpt-web/luasrc/model/cbi/chatgpt-web.lua b/luci-app-chatgpt-web/luasrc/model/cbi/chatgpt-web.lua new file mode 100644 index 00000000..38f5e7da --- /dev/null +++ b/luci-app-chatgpt-web/luasrc/model/cbi/chatgpt-web.lua @@ -0,0 +1,36 @@ +-- Copyright (C) 2023-2025 sirpdboy https://github.com/sirpdboy/https://github.com/sirpdboy/luci-app-chatgpt-web + +local m, s ,o + +m = Map("chatgpt-web") +m.title = translate("Chatgpt Web") +m.description = translate("ChatGPT-Web is the version used on OPENWRT of OPENAI")..translate("
For specific usage, see:")..translate("GitHub @sirpdboy/chatgpt-web\ ") + +s = m:section(TypedSection, "basic", translate("Base Setting")) +s.addremove = false +s.anonymous = true + + +o = s:option(Value,"userpic",translate("Character images")) +o:value("girl.jpg",translate("girl")) +o:value("girl2.jpg",translate("girl2")) +o:value("girl3.jpg",translate("girl3")) +o:value("boy.jpg",translate("boy")) +o:value("boy2.jpg",translate("boy2")) +o:value("boy3.jpg",translate("boy3")) +o:value("dog.jpg",translate("dog")) +o:value("cartoon.jpg",translate("cartoon")) +o:value("cartoonboy.jpg",translate("cartoonboy")) +o:value("cartoonboy2.jpg",translate("cartoonboy2")) +o:value("cartoongirl.jpg",translate("cartoongirl")) +o:value("cartoongirl2.jpg",translate("cartoongirl2")) +o.rmempty = true +o.default="girl.jpg" + + +o=s:option(TextValue, "systemrole" ,translate("Default Role Settings")) +o.rows = 5 +o.rmempty=false + + +return m diff --git a/luci-app-chatgpt-web/luasrc/model/cbi/chatgpt.lua b/luci-app-chatgpt-web/luasrc/model/cbi/chatgpt.lua new file mode 100644 index 00000000..162de6d0 --- /dev/null +++ b/luci-app-chatgpt-web/luasrc/model/cbi/chatgpt.lua @@ -0,0 +1,9 @@ +-- Copyright (C) 2023-2025 sirpdboy https://github.com/sirpdboy/luci-app-chatgpt-web +require("luci.util") +local m, s + +m = Map("chatgpt-web",translate("")) + +m:section(SimpleSection).template = "chatgpt-web" + +return m diff --git a/luci-app-chatgpt-web/luasrc/view/chatgpt-web.htm b/luci-app-chatgpt-web/luasrc/view/chatgpt-web.htm new file mode 100644 index 00000000..078e5ac7 --- /dev/null +++ b/luci-app-chatgpt-web/luasrc/view/chatgpt-web.htm @@ -0,0 +1,6845 @@ +<%# + Copyright 2023-2025 sirpdboy Wich https://github.com/sirpdboy/luci-app-chatgpt-web + Licensed to the public under the Apache License 2.0. +-%> +<% +local fs = require "nixio.fs" +local dsp = require "luci.dispatcher" +local uci = luci.model.uci.cursor() +local apikeys = uci:get_first('chatgpt-web', 'basic', 'apikey') +local userpic = "/luci-static/chatgpt-web/user/" .. uci:get_first('chatgpt-web', 'basic', 'userpic') +local sysrole = uci:get_first('chatgpt-web', 'basic', 'systemrole') + +-%> + +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
ChatGPT
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+ + 4o + + +
+
+
+ + +
+ + +
+
+
+
+ +
+
+
+
+ + +
+ + +
+
+
+
+
+
+ + + +
+
+
+
+
+ + + +
+
+ + + +
+
+
+
+
+
ChatGPT
+
DeepSeek
+
Azure
+
Gemini
+
Claude
+
+
+ +
+
+
+
ChatGPT
+
+ +
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+ + + + +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + +
\ No newline at end of file diff --git a/luci-app-chatgpt-web/po/zh-cn/chatgpt-web.po b/luci-app-chatgpt-web/po/zh-cn/chatgpt-web.po new file mode 100644 index 00000000..91654dbd --- /dev/null +++ b/luci-app-chatgpt-web/po/zh-cn/chatgpt-web.po @@ -0,0 +1,71 @@ +msgid "" +msgstr "Content-Type: text/plain; charset=UTF-8" + +msgid "chatgpt-web" +msgstr "ChatGPT-Web" + +msgid "ChatGPT-Web is the version used on OPENWRT of OPENAI" +msgstr "ChatGPT-Web是OPENAI的OPENWRT上使用的WEB版本,不需要第三方依赖,绿色安全直接使用OPENAI的API接口" + +msgid "Base Setting" +msgstr "基本设置" + +msgid "GPT Model" +msgstr "GPT模型" + +msgid "Character images" +msgstr "人物图像" + +msgid "boy" +msgstr "男生" + +msgid "boy2" +msgstr "男生2" + +msgid "boy3" +msgstr "男生3" + +msgid "girl" +msgstr "女生" + +msgid "girl2" +msgstr "女生2" + +msgid "girl3" +msgstr "女生3" + +msgid "dog" +msgstr "狗" + +msgid "cartoon" +msgstr "卡通图像" + +msgid "cartoonboy" +msgstr "卡通男孩" + +msgid "cartoonboy2" +msgstr "卡通小男孩" + +msgid "cartoongirl" +msgstr "卡通女生" + +msgid "cartoongirl2" +msgstr "卡通女生2" + +msgid "OpenAI interface" +msgstr "OpenAI接口" + +msgid "API Key" +msgstr "API密钥" + +msgid "Enter your own API key, and if you do not have one, enter the official website API to generate it:" +msgstr "输入自己的API密钥(sk-开头),没有的进官网API生成:" + +msgid "Click to open" +msgstr "点击前往" + +msgid "Default Role Settings" +msgstr "默认角色设置" + +msgid "
For specific usage, see:" +msgstr "
具体使用方法参见:" diff --git a/luci-app-chatgpt-web/po/zh_Hans/chatgpt-web.po b/luci-app-chatgpt-web/po/zh_Hans/chatgpt-web.po new file mode 100644 index 00000000..91654dbd --- /dev/null +++ b/luci-app-chatgpt-web/po/zh_Hans/chatgpt-web.po @@ -0,0 +1,71 @@ +msgid "" +msgstr "Content-Type: text/plain; charset=UTF-8" + +msgid "chatgpt-web" +msgstr "ChatGPT-Web" + +msgid "ChatGPT-Web is the version used on OPENWRT of OPENAI" +msgstr "ChatGPT-Web是OPENAI的OPENWRT上使用的WEB版本,不需要第三方依赖,绿色安全直接使用OPENAI的API接口" + +msgid "Base Setting" +msgstr "基本设置" + +msgid "GPT Model" +msgstr "GPT模型" + +msgid "Character images" +msgstr "人物图像" + +msgid "boy" +msgstr "男生" + +msgid "boy2" +msgstr "男生2" + +msgid "boy3" +msgstr "男生3" + +msgid "girl" +msgstr "女生" + +msgid "girl2" +msgstr "女生2" + +msgid "girl3" +msgstr "女生3" + +msgid "dog" +msgstr "狗" + +msgid "cartoon" +msgstr "卡通图像" + +msgid "cartoonboy" +msgstr "卡通男孩" + +msgid "cartoonboy2" +msgstr "卡通小男孩" + +msgid "cartoongirl" +msgstr "卡通女生" + +msgid "cartoongirl2" +msgstr "卡通女生2" + +msgid "OpenAI interface" +msgstr "OpenAI接口" + +msgid "API Key" +msgstr "API密钥" + +msgid "Enter your own API key, and if you do not have one, enter the official website API to generate it:" +msgstr "输入自己的API密钥(sk-开头),没有的进官网API生成:" + +msgid "Click to open" +msgstr "点击前往" + +msgid "Default Role Settings" +msgstr "默认角色设置" + +msgid "
For specific usage, see:" +msgstr "
具体使用方法参见:" diff --git a/luci-app-chatgpt-web/root/etc/config/chatgpt-web b/luci-app-chatgpt-web/root/etc/config/chatgpt-web new file mode 100644 index 00000000..a7a1636a --- /dev/null +++ b/luci-app-chatgpt-web/root/etc/config/chatgpt-web @@ -0,0 +1,8 @@ + +config basic + option apiHost 'https://api.openai.com/' + option modelver 'gpt-3.5-turbo' + option userpic 'girl.jpg' + option apikey '' + option systemrole '你是一个乐于助人的专业助手,尽量简明扼要地回答' + diff --git a/luci-app-chatgpt-web/root/usr/share/rpcd/acl.d/luci-app-chatgpt-web.json b/luci-app-chatgpt-web/root/usr/share/rpcd/acl.d/luci-app-chatgpt-web.json new file mode 100644 index 00000000..c8c06f50 --- /dev/null +++ b/luci-app-chatgpt-web/root/usr/share/rpcd/acl.d/luci-app-chatgpt-web.json @@ -0,0 +1,11 @@ +{ + "luci-app-chatgpt-web": { + "description": "Grant UCI access for luci-app-chatgpt-web", + "read": { + "uci": [ "chatgpt-web" ] + }, + "write": { + "uci": [ "chatgpt-web" ] + } + } +} diff --git a/luci-app-chatgpt-web/root/www/luci-static/chatgpt-web/11.11.1github.min.css b/luci-app-chatgpt-web/root/www/luci-static/chatgpt-web/11.11.1github.min.css new file mode 100644 index 00000000..275239a7 --- /dev/null +++ b/luci-app-chatgpt-web/root/www/luci-static/chatgpt-web/11.11.1github.min.css @@ -0,0 +1,10 @@ +pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*! + Theme: GitHub + Description: Light theme as seen on github.com + Author: github.com + Maintainer: @Hirse + Updated: 2021-05-15 + + Outdated base version: https://github.com/primer/github-syntax-light + Current colors taken from GitHub's CSS +*/.hljs{color:#24292e;background:#fff}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#d73a49}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#6f42c1}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-variable{color:#005cc5}.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#032f62}.hljs-built_in,.hljs-symbol{color:#e36209}.hljs-code,.hljs-comment,.hljs-formula{color:#6a737d}.hljs-name,.hljs-quote,.hljs-selector-pseudo,.hljs-selector-tag{color:#22863a}.hljs-subst{color:#24292e}.hljs-section{color:#005cc5;font-weight:700}.hljs-bullet{color:#735c0f}.hljs-emphasis{color:#24292e;font-style:italic}.hljs-strong{color:#24292e;font-weight:700}.hljs-addition{color:#22863a;background-color:#f0fff4}.hljs-deletion{color:#b31d28;background-color:#ffeef0} \ No newline at end of file diff --git a/luci-app-chatgpt-web/root/www/luci-static/chatgpt-web/chatgpt-web.css b/luci-app-chatgpt-web/root/www/luci-static/chatgpt-web/chatgpt-web.css new file mode 100644 index 00000000..7a24e376 --- /dev/null +++ b/luci-app-chatgpt-web/root/www/luci-static/chatgpt-web/chatgpt-web.css @@ -0,0 +1,2166 @@ + + .chat_window { + display: flex; + width: 100%; + max-width: 1920px; + height: 888px; + border-radius: 8px; + background-color: rgba(255,255,255,0.5); + overflow: auto; + } + + .chat_window>.overlay { + width: 100%; + height: 100%; + position: absolute; + left: 0; + top: 0; + background-color: rgba(0, 0, 0, .3); + z-index: 90; + opacity: 0; + visibility: hidden; + cursor: pointer; + transition: all 120ms ease-in-out; + } + + .chat_window .mainContent .cbi-input-invalid { + color: var(--title-color); + border-bottom-color: var(--title-color); + } + + + @media screen and (min-width: 800px) { + + .mainContent { + width: calc(100% - 250px); + } + + .chat_window>.nav { + position: relative; + margin-left: -250px; + transition: margin-left 250ms; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + } + + .show-nav .nav { + margin-left: 0; + } + + #sysDialog { + max-width: 600px; + } + + .sysContent { + display: flex; + } + + .sysSwitch { + flex-shrink: 0; + width: 160px; + } + + .sysSwitch>div { + padding-left: 6px; + } + + .sysDetail { + margin-left: 12px; + margin-top: 5px; + } + } + + @media screen and (max-width: 800px) { + + .mainContent { + width: 100%; + } + + .chat_window>.nav { + position: absolute !important; + left: -250px; + transition: left 250ms; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + } + + .show-nav .nav { + left: 0; + box-shadow: rgba(0, 0, 0, 0.2) 0px 8px 10px -5px, rgba(0, 0, 0, 0.14) 0px 16px 24px 2px, rgba(0, 0, 0, 0.12) 0px 6px 30px 5px; + } + + .show-nav .overlay { + visibility: visible; + opacity: 1; + } + + #sysDialog { + max-width: 400px; + } + + .sysSwitch { + display: flex; + } + + .sysSwitch>div { + width: 50%; + justify-content: center; + } + + .sysDetail { + margin-top: 8px; + } + } + .full_window { + max-width: none; + max-height: none; + } + + .chat_window>.nav { + width: 250px; + background-color: rgba(var(--primary-rgbbody),1); + color: var(--body-color); + top: 0; + z-index: 99; + flex-shrink: 0; + display: flex; + flex-direction: column; + margin-left: -250px; + transition: left 120ms ease-in-out; + } + + .mainContent { + height: 100%; + position: relative; + width: 100%; + display: flex; + flex-direction: column; + flex: 1; + } + + .show-nav .nav { + margin-left: 0; + } + .show-nav .chatnav { + margin-left: 0; + box-shadow: rgba(0, 0, 0, 0.2) 0px 8px 10px -5px, rgba(0, 0, 0, 0.14) 0px 16px 24px 2px, rgba(0, 0, 0, 0.12) 0px 6px 30px 5px; + } + + .show-nav .overlay { + opacity: 1; + visibility: visible; + } + .top_menu { + width: 100%; + background-color: rgba(255,255,255,0.5); + height: 50px; + padding: 5px 0; + } + + .top_menu .toggler { + margin-left: 10px; + width: 40px; + height: 40px; + float: left; + padding: 5px 7px; + border-radius: 4px; + cursor: pointer; + -webkit-tap-highlight-color: transparent; + } + + .top_menu .toggler:hover { + background: #e0e0e0; + } + + .top_menu .toggler .button { + width: 26px; + height: 4px; + border-radius: 4px; + position: absolute; + pointer-events: none; + } + + .top_menu .toggler .button.close { + margin-top: 3px; + background-color: #000; + } + + .top_menu .toggler .button.minimize { + margin-top: 13px; + background-color: #000; + } + + .top_menu .toggler .button.maximize { + margin-top: 23px; + background-color: #000; + } + + .top_menu .title { + font-size: 20px; + height: 40px; + line-height: 40px; + position: relative; + pointer-events: none; + } + + #selector { + position: absolute; + top: 50%; + left: 50%; + padding: 0 4px 0 10px; + cursor: pointer; + font-size: 20px; + transform: translate(-50%, -50%); + pointer-events: auto; + border-radius: 4px; + -webkit-user-select: none; + user-select: none; + display: flex; + align-items: center; + } + + #selector:hover { + color: var(--title-color); + } + + #modelVer { + margin-left: 6px; + margin-right: 4px; + height: 40px; + line-height: 38px; + } + + .messages { + position: relative; + flex: 1; + overflow-x: hidden; + overflow-y: auto; + font-size: 16px; + width: 100%; + text-align: center; + } + + .messages::-webkit-scrollbar, + + #chatlog .markdown-body>pre::-webkit-scrollbar, + #chatlog .markdown-body>pre>code::-webkit-scrollbar, + #chatlog .responseBody pre>code::-webkit-scrollbar, + #setDialog::-webkit-scrollbar, + .allList::-webkit-scrollbar, + .sysDetail::-webkit-scrollbar, + #apiSelect::-webkit-scrollbar { + width: 10px; + height: 10px; + } + + .messages::-webkit-scrollbar-track, + + #chatlog .markdown-body>pre::-webkit-scrollbar-track, + #chatlog .markdown-body>pre>code::-webkit-scrollbar-track, + #chatlog .responseBody pre>code::-webkit-scrollbar-track, + #setDialog::-webkit-scrollbar-track, + .allList::-webkit-scrollbar-track, + .sysDetail::-webkit-scrollbar-track, + #apiSelect::-webkit-scrollbar-track { + background-clip: padding-box; + background: transparent; + border: none; + } + + .messages::-webkit-scrollbar-corner, + + #chatlog .markdown-body>pre::-webkit-scrollbar-corner, + #chatlog .markdown-body>pre>code::-webkit-scrollbar-corner, + #chatlog .responseBody pre>code::-webkit-scrollbar-corner, + #setDialog::-webkit-scrollbar-corner, + .allList::-webkit-scrollbar-corner, + .sysDetail::-webkit-scrollbar-corner, + #apiSelect::-webkit-scrollbar-corner { + background-color: transparent; + } + + .messages::-webkit-scrollbar-thumb, + + #chatlog .markdown-body>pre::-webkit-scrollbar-thumb, + #chatlog .markdown-body>pre>code::-webkit-scrollbar-thumb, + #chatlog .responseBody pre>code::-webkit-scrollbar-thumb, + #setDialog::-webkit-scrollbar-thumb, + .allList::-webkit-scrollbar-thumb, + .sysDetail::-webkit-scrollbar-thumb, + #apiSelect::-webkit-scrollbar-thumb { + background-color: rgba(0, 0, 0, 0.1); + background-clip: padding-box; + border: solid transparent; + border-radius: 10px; + } + + .messages::-webkit-scrollbar-thumb:hover, + + #chatlog .markdown-body>pre::-webkit-scrollbar-thumb:hover, + #chatlog .markdown-body>pre>code::-webkit-scrollbar-thumb:hover, + #chatlog .responseBody pre>code::-webkit-scrollbar-thumb:hover, + #setDialog::-webkit-scrollbar-thumb:hover, + .allList::-webkit-scrollbar-thumb:hover, + .sysDetail::-webkit-scrollbar-thumb:hover, + #apiSelect::-webkit-scrollbar-thumb:hover { + background-color: rgba(0, 0, 0, 0.4); + } + + #chatlog { + word-wrap: break-word; + text-align: start; + } + + #chatlog>div { + padding: 18px 40px 18px 54px; + } + .chatAvatar { + position: absolute; + width: 30px; + height: 30px; + border-radius: 2px; + } + + .request>.chatAvatar { + top: 18px; + left: 13px; + margin-top: 15px; + margin-bottom: 15px; + } + .response>.chatAvatar { + background: #10a37f; + top: 18px; + left: 13px; + margin-top: 15px; + margin-bottom: 15px; + } + + .gptAvatar>svg { + margin-top: 3px; + margin-left: 3px; + } + + .chatAvatar>img { + display: block; + width: 100%; + height: 100%; + border-radius: 2px; + } + + .modelAvatar>svg, + .chatAvatar>svg { + display: block; + margin-top: 4px; + margin-left: 4px; + } + + #chatlog .request { + position: relative; + background-color: var(--inputborder-color); + display: flex; + justify-content: center; + } + + + .response .responseBody { + display: flex; + justify-content: flex-end; + margin: 18px 45px 18px 20px; + font-size: 15px; + + } + .requestBody { + white-space: pre-wrap; + margin: 18px; + flex: 1; + } + + #chatlog .response { + background-color: var(--inputborder-color); + position: relative; + } + + .response .markdown-body { + margin: 18px 65px 18px 0px; + flex: 1; + font-size: 15px; + background-color: rgba(255,255,255,0)!important; + + } + + #chatlog .markdown-body a { + padding: 0 1px 0 2px; + } + + #chatlog .markdown-body pre { + overflow-x: auto; + padding: 10px; + position: relative; + white-space: pre-wrap; + background-color: var(--inputborder-color)!important; + + } + + #chatlog .markdown-body .mermaid { + margin-bottom: 8px; + padding: 0 10px 0 10px; + position: relative; + border-radius: 6px; + } + + .mermaid>svg { + cursor: pointer; + max-height: 600px; + display: block; + } + + #chatlog .markdown-body pre>code { + overflow-x: auto; + white-space: pre-wrap; + display: block; + } + + #chatlog .markdown-body ul { + list-style-type: disc; + } + + .m-mdic-copy-wrapper { + position: absolute; + top: 5px; + right: 16px; + -webkit-user-select: none; + user-select: none; + } + + .m-mdic-copy-wrapper span.u-mdic-copy-code_lang { + position: absolute; + top: 3px; + right: calc(100% + 4px); + font-family: system-ui; + font-size: 12px; + line-height: 18px; + color: #bbb; + } + + .m-mdic-copy-wrapper div.u-mdic-copy-notify { + position: absolute; + top: 0; + right: 0; + padding: 3px 6px; + border: 0; + border-radius: 3px; + background: none; + font-family: system-ui; + font-size: 12px; + line-height: 18px; + color: #555; + outline: none; + right: 100%; + padding-right: 4px; + } + + .m-mdic-copy-wrapper button.u-mdic-copy-btn { + position: relative; + top: 0; + right: 0; + padding: 3px 6px; + border: 0; + border-radius: 3px; + background: none; + font-family: system-ui; + font-size: 12px; + line-height: 18px; + color: #bbb; + outline: none; + cursor: pointer; + transition: color 0.2s; + } + + .m-mdic-copy-wrapper span.u-mdic-copy-code_lang::before, + .m-mdic-copy-wrapper div.u-mdic-copy-notify::before, + .m-mdic-copy-wrapper button.u-mdic-copy-btn::before { + content: attr(text); + } + + .m-mdic-copy-wrapper button.u-mdic-copy-btn:hover { + color: #555; + } + + #stopChat { + display: none; + margin: 0 auto; + margin-top: 3px; + width: 80px; + height: 32px; + text-align: center; + line-height: 32px; + color: white; + background: #f8b26a; + cursor: pointer; + border-radius: 3px; + position: sticky; + bottom: 2px; + justify-content: center; + align-items: center; + } + + #stopChat>svg { + margin-right: 8px; + } + + #stopChat:hover { + background: #f0aa60; + } + + .bottom_wrapper { + position: relative; + width: 100%; + padding: 10px 10px; + max-width: 91%; + margin: 0 auto; + } + + .bottom_wrapper .message_input_wrapper { + width: calc(100% - 134px); + position: relative; + text-align: left; + } + + .bottom_wrapper .message_input_wrapper .message_input_text { + border-radius: 4px; + border: none; + outline: none; + resize: none; + background-color: var(--inputborder-color); + height: 47px; + font-size: 16px; + max-height: 200px; + padding: 13px 0 13px 13px; + width: 100%!important; + display: block; + transition: background-color 250ms; + } + + .bottom_wrapper .message_input_wrapper .message_input_text:focus { + background-color: var(--inputborder-color); + } + + .bottom_wrapper .message_input_wrapper .message_input_text::-webkit-scrollbar { + display: none; + width: 0; + height: 0; + } + + #sendbutton { + width: 70px; + height: 47px; + font-size: 18px; + font-weight: bold; + color: #fff; + background-color: #7ac516; + padding: 0.5rem 0.5rem; + transition: all 0.2s linear; + position: absolute; + right: 65px; + bottom: 10px; + cursor: not-allowed; + } + + .activeSendBtn { + background-color: #99c959 !important; + cursor: pointer !important; + } + + .activeSendBtn:hover { + background-color: #90c050 !important; + } + + .clearConv { + position: absolute; + right: 10px; + bottom: 10px; + width: 47px; + padding: 0.5rem 0.5rem; + height: 47px; + } + + .clearConv>svg { + margin: 0 auto; + top: 50%; + left: 50%; + } + + .clearConv:hover { + background-color: var(--body-hover-bgcolor); + } + + .clearConv svg:first-child { + display: none; + } + + .clearConv svg:nth-child(2) { + display: block; + } + + .closeConv { + background: #e0e0e0;; + } + + .closeConv:hover { + background: #e8e8e8; + } + + .closeConv svg:first-child { + display: block; + } + + .closeConv svg:nth-child(2) { + display: none; + } + + .loaded>span { + display: inline-block; + } + + .loaded>svg { + display: none; + } + + .loading { + background: rgba(0,0,0,0) !important; + } + + .loading>span { + display: none; + } + + .loading>svg { + display: block; + } + + .switch-slide { + display: inline-block; + vertical-align: middle; + } + + .switch-slide-label { + display: block; + width: 38px; + height: 18px; + background: #909090; + border-radius: 30px; + cursor: pointer; + position: relative; + -webkit-transition: 0.3s ease; + transition: 0.3s ease; + } + + .switch-slide-label:after { + content: ""; + display: block; + width: 16px; + height: 16px; + border-radius: 100%; + background: #fff; + box-shadow: 0 1px 1px rgba(0, 0, 0, .1); + position: absolute; + left: 1px; + top: 1px; + -webkit-transform: translateZ(0); + transform: translateZ(0); + -webkit-transition: all 250ms; + transition: all 250ms; + } + + .switch-slide input:checked+label { + background: #99c959; + -webkit-transition: all 250ms; + transition: all 250ms; + } + + .switch-slide input:checked+label:after { + left: 21px; + } + + .settings { + padding: 0px 7px; + margin-top: -40px; + float: right; + border-radius: 4px; + cursor: pointer; + top: 5px; + } + + + .setBtn { + margin-left: 2px; + cursor: pointer; + padding: 5px; + border: none; + border-radius: 4px; + } + + .setBtn>svg { + display: block; + color: #909090; + } + + .setBtn:hover { + background: #e0e0e0;; + } + + #setting { + right: 15px; + } + + #toggleFull { + right: 56px; + } + + #toggleLight *, + #toggleFull * { + pointer-events: none; + } + + .showSetting { + background-color: var(--inputborder-color); + } + + .showModels { + background-color: var(--inputborder-color); + } + + .showModels>svg:last-child { + transform: scaleY(-1); + } + + #modelDialog { + position: absolute; + z-index: 2; + background: rgba(var(--primary-rgbbody), 1); + width: 240px; + left: 50%; + transform: translateX(-50%); + top: 46px; + overflow-y: auto; + max-height: calc(100% - 55px); + -webkit-user-select: none; + user-select: none; + border-radius: 4px; + padding: 8px 5px 8px 5px; + box-shadow: 0 0 6px rgba(0, 0, 0, 0.15); + } + + .modelSingle { + height: 40px; + border-radius: 3px; + cursor: pointer; + line-height: 40px; + display: flex; + align-items: center; + position: relative; + } + + .modelSingle * { + pointer-events: none; + } + + .modelSingle:hover { + background-color: var(--body-hover-bgcolor); + } + + .activeModel::before { + content: ""; + position: absolute; + right: 15px; + top: 7px; + width: 8px; + height: 16px; + border-style: solid; + border-width: 0 4px 4px 0; + transform: rotate(45deg); + } + + .modelAvatar { + margin-left: 6px; + margin-right: 10px; + width: 30px; + flex-shrink: 0; + border-radius: 2px; + height: 30px; + } + + #setDialog { + position: absolute; + z-index: 2; + background-color: rgba(var(--primary-rgbbody),1); + width: 320px; + right: 6px; + top: 46px; + overflow-y: auto; + max-height: calc(100% - 55px); + -webkit-user-select: none; + user-select: none; + border-radius: 5px; + padding: 8px 12px 8px 12px; + box-shadow: 0 0 6px rgba(0, 0, 0, 0.15); + } + + #setDialog input { + width: 100%; + } + + #setDialog .inlineTitle { + display: inline-block; + width: 80px; + line-height: 16px; + vertical-align: middle; + } + + #convOption, + #speechOption, + #speechDetail, + #recOption { + margin-bottom: 6px; + } + + #convOption>div, + #speechOption>div, + #speechDetail>div, + #recOption div { + margin-top: 10px; + } + + #voiceRecSetting select, #speechDetail select { + color: var(--title-color); + + } + .inputTextClass { + outline: none; + border-radius: 2px; + margin-top: 3px; + height: 32px; + font-size: 15px; + padding-left: 6px; + border: none; + } + + .areaTextClass { + width: 100%; + height: 150px; + white-space: normal; + resize: none; + padding: 6px; + } + + input[type="range"] { + -webkit-appearance: none; + appearance: none; + display: block; + margin: 4px 0 3px 0; + height: 8px; + background: #909090; + border-radius: 5px; + background-image: linear-gradient(#99c959, #99c959); + background-size: 100% 100%; + background-repeat: no-repeat; + } + + input[type="range"]::-webkit-slider-thumb { + -webkit-appearance: none; + height: 15px; + width: 15px; + border-radius: 50%; + background: #99c959; + cursor: ew-resize; + } + + input[type=range]::-webkit-slider-runnable-track { + -webkit-appearance: none; + box-shadow: none; + border: none; + background: transparent; + } + + .justSetLine { + display: flex; + justify-content: space-between; + align-items: center; + } + + .justSetBtn { + height: 32px; + border-radius: 3px; + line-height: 32px; + background-color: var(--inputborder-color); + text-align: center; + padding: 0px 8px; + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; + } + + .justSetBtn:hover { + background-color: #d0d0d0; + } + + .justSetBtn>svg { + margin-right: 3px; + } + + .readyTestVoice>div:not(:first-child) { + display: none; + } + + .pauseTestVoice>div:nth-child(1), + .pauseTestVoice>div:nth-child(3) { + display: none; + } + + .resumeTestVoice>div:nth-child(1), + .resumeTestVoice>div:nth-child(2) { + display: none; + } + + .presetSelect>div { + display: inline-block; + } + + .presetSelect select { + outline: none; + border-radius: 3px; + width: 120px; + border-color: rgba(0, 0, 0, .3); + } + + .selectDef { + display: flex; + justify-content: space-between; + font-size: 13px; + color: #909090; + } + + #preSetSpeech { + width: 100%; + outline: none; + height: 30px; + line-height: 30px; + font-size: 14px; + margin-top: 5px; + border-radius: 3px; + border-color: rgba(0, 0, 0, .3); + } + + .mdOption { + position: absolute; + right: 0px; + top: 0; + width: 50px; + height: 100%; + pointer-events: none; + } + + .mdOption>div { + pointer-events: auto; + cursor: pointer; + } + + .mdOption svg * { + pointer-events: none; + } + + .refreshReq svg:not(:first-child) { + display: none; + } + + .halfRefReq svg:not(:nth-child(2)) { + display: none; + } + + .optionItems { + position: absolute; + bottom: -5px; + display: flex; + justify-content: space-between; + visibility: visible; + z-index: 1; + color: #808080; + } + + .optionItems:hover { + visibility: visible; + } + + .request:hover .optionItems, + .request:hover .voiceCls, + .response:hover .optionItems, + .response:hover .voiceCls { + visibility: visible; + } + + .optionItem { + border-radius: 10px; + height: 24px; + width: 32px; + border: 1px solid #e0e0e0;; + background-color: #f7f7f8; ; + display: flex !important; + justify-content: center; + align-items: center; + } + + .optionItem * { + pointer-events: none; + } + + .optionItem:hover { + background: #e0e0e0;; + } + + .voiceCls { + position: relative; + height: 100%; + visibility: visible; + display: flex; + align-items: center; + } + + .voiceCls>svg { + color: #c0c0c0; + display: block; + margin-left: 5px; + position: relative; + pointer-events: none; + } + + .voiceCls:hover>svg { + color: #808080; + } + + .showVoiceCls, + .showVoiceCls .markdown-body { + background: #e0e0e0 !important; + } + + .showVoiceCls .voiceCls { + visibility: visible !important; + } + + .showEditReq { + position: sticky !important; + top: 0; + bottom: 0; + z-index: 1; + } + + .showEditReq, + .showEditReq .markdown-body { + background: #e0e0e0 !important; + } + + .readyVoice svg:not(:first-child) { + display: none; + } + + .pauseVoice svg:not(:nth-child(2)) { + display: none; + } + + .resumeVoice svg:not(:nth-child(3)) { + display: none; + } + + #voiceTypes>span { + border-radius: 3px; + margin-left: 4px; + cursor: pointer; + color: var(--title-color); + padding: 1px 5px; + } + + #voiceTypes>span:hover { + background-color: var(--body-hover-bgcolor); + } + + .selVoiceType { + background-color: var(--inputborder-color); + } + + .navHeader { + width: 100%; + padding: 5px 10px; + display: flex; + justify-content: space-between; + } + + #newChat { + text-align: center; + width: 80%; + height: 40px; + border-radius: 3px; + background: var(--inputborder-color); + display: flex; + align-items: center; + justify-content: center; + -webkit-user-select: none; + user-select: none; + cursor: pointer; + flex: 1; + } + + .navHeader>div:hover { + background: var(--body-hover-bgcolor)!important; + } + + #newChat>svg { + margin-right: 2px; + } + + #newFolder { + height: 40px; + width: 40px; + margin-left: 10px; + border-radius: 3px; + background: var(--inputborder-color); + cursor: pointer; + position: relative; + -webkit-user-select: none; + user-select: none; + } + + #newFolder>svg { + display: block; + margin: 8px auto; + } + + .extraChat { + padding: 2px 10px 6px 10px; + position: relative; + } + + #searchChat { + width: 100%; + height: 36px; + padding-left: 10px; + padding-right: 16px; + font-size: 16px; + outline: none; + border: none; + background: var(--inputborder-color); + border-radius: 3px; + } + + #searchChat:focus { + background: var(--inputborder-color); + } + + #searchChat:placeholder-shown+#clearSearch { + display: none; + } + + #clearSearch { + position: absolute; + right: 12px; + top: 8px; + cursor: pointer; + } + + #clearSearch:hover { + color: var(--title-color); + } + + #clearSearch>svg { + display: block; + } + + .seledSearch { + background: var(--inputborder-color)!important; + } + + #matchCaseSearch { + position: absolute; + right: 12px; + top: 8px; + cursor: pointer; + border-radius: 3px; + color: var(--inputborder-color); + } + + #matchCaseSearch:hover { + background: var(--body-hover-bgcolor); + } + + #matchCaseSearch>svg { + display: block; + } + + .navFooter { + padding-bottom: 8px; + background: var(--inputborder-color); + } + + .navFooter .divider { + width: 100%; + border-top: 1px solid var(--inputborder-color); + margin: 4px 0; + } + + .navFunc { + padding-top: 5px; + display: flex; + justify-content: space-evenly; + } + + .navFunc svg { + display: block; + } + + .navFunc>div, + .navFunc>label { + border-radius: 20px; + text-align: center; + padding: 8px 8px; + font-size: 14px; + cursor: pointer; + } + + .navFunc>div:hover, + .navFunc>label:hover { + background: var(--body-hover-bgcolor); + } + + .navFooter .links { + text-align: center; + -webkit-user-select: none; + user-select: none; + } + + .navFooter .links a { + text-decoration: none; + } + + + .allList { + width: 100%; + flex: 1; + overflow-y: auto; + } + + #chatList { + min-height: 50px; + } + + .dragingLi { + filter: brightness(90%); + } + + .dragingChat { + background: #e8e8e8; + } + + .expandFolder>.headLi>svg { + transform: rotate(90deg); + } + + .expandFolder>.chatsInFolder { + display: block; + } + + .chatsInFolder { + display: none; + margin-left: 22px; + padding-left: 2px; + border-left: 1px solid #909090; ; + } + + .headLi, + .chatLi { + cursor: pointer; + width: 100%; + height: 50px; + display: flex; + justify-content: space-between; + align-items: center; + position: relative; + } + + .headLi *, + .chatLi * { + pointer-events: none; + } + + .headLi>svg, + .chatLi>svg { + margin-left: 10px; + pointer-events: none; + } + + .folderOption svg, + .chatOption svg { + pointer-events: auto; + } + + .headLi svg *, + .chatLi svg * { + pointer-events: none; + } + + .headLi .folderInfo { + position: absolute; + left: 40px; + height: 40px; + max-width: calc(100% - 115px); + } + + .chatLi .chatInfo { + position: absolute; + left: 45px; + height: 40px; + max-width: calc(100% - 100px); + } + + .folderInfo *, + .chatInfo * { + -webkit-user-select: none; + user-select: none; + } + + .chatInfo span { + background: var(--inputborder-color)!important; + } + + .headLi .folderName, + .chatLi .chatName { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + line-height: 20px; + height: 20px; + } + + .headLi .folderNum, + .chatLi .chatPre { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + font-size: 12px; + line-height: 20px; + height: 20px; + } + + .headLi .folderOption, + .chatLi .chatOption { + visibility: hidden; + display: flex; + margin-right: 5px; + } + + .folderLi .chatLi>svg { + margin-left: 5px; + } + + .folderLi .chatLi .chatInfo { + left: 35px; + max-width: calc(100% - 85px); + } + + .folderLi .chatLi #activeChatEdit { + left: 32px; + width: calc(100% - 60px) + } + + .folderLi:hover { + background: var(--body-hover-bgcolor)!important; + } + + .chatLi:hover { + background: var(--body-hover-bgcolor)!important; + } + + .headLi:hover .folderOption, + .chatLi:hover .chatOption { + visibility: visible !important; + } + + .activeFolder, + .activeChatLi { + background: var(--inputborder-color)!important; + } + + .activeChatLi .chatOption { + visibility: visible !important; + } + + .folderOption>svg:hover, + .chatOption>svg:hover { + color: var(--title-color); + } + + #activeChatEdit { + position: absolute; + left: 42px; + font-size: 16px; + border-radius: 2px; + color: var(--title-color); + background-color: rgba(var(--primary-rgbbody),1); + outline: none; + border: none; + pointer-events: auto; + height: 24px; + line-height: 24px; + width: calc(100% - 75px); + padding: 20px 3px; + z-index: 1; + } + + #loadMask { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 100; + background-color: #edeff2; + } + + #loadMask>div { + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + text-align: center; + } + + @keyframes loading { + + 0%, + 100% { + transform: scale(0); + } + + 50% { + transform: scale(1); + } + } + + .loadingCSSIcon { + position: relative; + display: flex; + align-items: center; + justify-content: space-evenly; + } + + .loadingCSSIcon div { + width: 15%; + height: 0; + padding-bottom: 15%; + border-radius: 50%; + animation: loading 1s cubic-bezier(0.3, 0, 0.7, 1) infinite; + } + + .loadingCSSIcon div:nth-child(1) { + background: #e15b64; + animation-delay: -0.4s + } + + .loadingCSSIcon div:nth-child(2) { + background: #f8b26a; + animation-delay: -0.2s + } + + .loadingCSSIcon div:nth-child(3) { + background: #99c959; + animation-delay: 0s; + } + + #loadMask>div>:first-child { + font-size: 40px; + color: #909090; ; + } + + #loadMask>div>:last-child { + width: 140px; + height: 70px; + margin: 0 auto; + } + + #voiceRec { + position: absolute; + right: 0; + top: 0; + width: 47px; + height: 100%; + } + + .message_if_voice { + padding-right: 47px !important; + } + + #voiceRecIcon { + width: 100%; + height: 100%; + text-align: center; + cursor: pointer; + position: relative; + } + + #voiceRecIcon:hover>svg { + color: #808080; + } + + #voiceRecIcon>svg { + width: 38px; + height: 38px; + color: #b0b0b0; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + } + + #voiceRecIcon>svg .animVoice { + display: none; + } + + .voiceRecing>svg { + color: #99c959 !important; + } + + .voiceRecing .animVoice { + display: inline !important; + transform-origin: 0 64%; + animation-duration: 1.5s; + animation-name: scaleVoice; + animation-timing-function: ease; + animation-iteration-count: infinite; + } + + .voiceLong .animVoice { + display: inline !important; + transform-origin: 0 64%; + animation-duration: 0.3s; + animation-name: longVoice; + animation-timing-function: ease-in-out; + animation-iteration-count: 1; + } + + @keyframes longVoice { + 0% { + transform: scaleY(0); + } + + 100% { + transform: scaleY(1); + } + } + + @keyframes scaleVoice { + 0% { + transform: scaleY(0.28); + } + + 20% { + transform: scaleY(0.60); + } + + 28% { + transform: scaleY(0.28); + } + + 36% { + transform: scaleY(0.45); + } + + 44% { + transform: scaleY(0.28); + } + + 52% { + transform: scaleY(0.45); + } + + 62% { + transform: scaleY(0.80); + } + + 72% { + transform: scaleY(0.80); + } + + 90% { + transform: scaleY(0.28); + } + + 100% { + transform: scaleY(0.28); + } + } + + #voiceRecSetting { + display: none; + position: absolute; + top: -70px; + left: -26px; + z-index: 1; + padding: 4px 4px; + -webkit-user-select: none; + user-select: none; + border-radius: 3px; + box-shadow: 0 0 6px rgba(0, 0, 0, 0.15); + } + + #voiceRecSetting select { + width: 102px; + outline: none; + height: 28px; + border-radius: 3px; + border-color: rgba(0, 0, 0, .3); + } + + .presetModelCls label { + margin-right: 8px; + } + + .presetModelCls select { + height: 30px; + line-height: 30px; + margin-top: 2px; + font-size: 15px; + } + + .modelSwitch, + .setSwitch { + display: flex; + } + + .modelSwitch { + margin-bottom: 10px; + -webkit-user-select: none; + user-select: none; + } + + .modelSwitch>div, + .setSwitch>div { + border-radius: 3px; + width: calc(100% / 3); + padding: 8px; + line-height: 1; + text-align: center; + cursor: pointer; + } + + .modelSwitch>div:hover, + .setSwitch>div:hover { + background-color: var(--body-hover-bgcolor); + } + + .activeSwitch { + background-color: var(--inputborder-color); + } + + .checkLoad { + height: 32px; + border-radius: 3px; + line-height: 32px; + background: #d0d0d0; + text-align: center; + display: flex; + justify-content: center; + cursor: pointer; + } + + .checkLoad:hover { + background: #c0c0c0; + } + + .voiceChecking { + background-color: #c0c0c0 !important; + } + + .voiceChecking>svg { + display: inline !important; + } + + .checkLoad>svg { + display: none; + margin-right: 8px; + height: 32px; + width: 64px; + } + + #preSetSystem { + height: 30px; + line-height: 30px; + } + + #sysMask { + display: none; + position: fixed; + z-index: 200; + top: 0; + left: 0; + bottom: 0; + right: 0; + cursor: pointer; + justify-content: center; + align-items: center; + background: rgba(0, 0, 0, .4); + -webkit-tap-highlight-color: transparent; + } + + #sysDialog { + position: relative; + background: rgba(var(--primary-rgbbody),1); + cursor: auto; + max-height: 100%; + width: 100%; + display: flex; + flex-direction: column; + border-radius: 4px; + padding: 20px; + } + + .sysTitle { + font-size: 20px; + font-weight: bold; + margin-bottom: 8px; + -webkit-user-select: none; + user-select: none; + } + + .sysSwitch, + .sysSwitch>div * { + pointer-events: none; + } + + .sysSwitch>div { + border-radius: 3px; + height: 32px; + line-height: 32px; + text-align: center; + cursor: pointer; + pointer-events: auto; + font-weight: bold; + display: flex; + align-items: center; + -webkit-user-select: none; + user-select: none; + } + + .sysSwitch>div>svg { + margin-right: 4px; + } + + .sysSwitch>div:hover { + background-color: var(--body-hover-bgcolor); + } + + .sysDetail { + overflow-y: auto; + flex: 1; + margin-top: 4px; + } + + #closeSet { + position: absolute; + right: 0px; + top: 0px; + cursor: pointer; + padding: 10px 14px; + } + + #closeSet:hover { + color: #000; + } + + .setContent { + margin-top: 8px; + } + + .setNotNormalFlow { + position: relative; + } + + .setTitle { + margin-bottom: 6px; + font-weight: bold; + -webkit-user-select: none; + user-select: none; + } + + .setDetail { + margin: 0 10px; + -webkit-user-select: none; + user-select: none; + } + + .autoSelect>label, + .autoSelect>input { + cursor: pointer; + } + + .dataDetail { + display: flex; + } + + .dataDetail svg { + margin-right: 4px; + } + + .dataDetail>div, + .dataDetail>label { + border-radius: 3px; + text-align: center; + padding: 6px 8px; + margin-right: 12px; + font-size: 15px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: space-between; + } + + .dataDetail>div:hover, + .dataDetail>label:hover { + background-color: var(--body-hover-bgcolor); + } + + .keyOptionDetail { + display: flex; + margin-top: 10px; + } + + #resetHotKey { + display: flex; + align-items: center; + justify-content: space-between; + cursor: pointer; + font-size: 15px; + border-radius: 3px; + padding: 6px 8px; + margin-left: 90px; + } + + #resetHotKey>svg { + margin-right: 4px; + } + + #resetHotKey:hover { + background-color:var(--body-hover-bgcolor); + } + + .hotKeyDetail>div { + position: relative; + height: 30px; + margin-bottom: 2px; + } + + .hotKeyDetail label { + line-height: 30px; + } + + .hotKeyDetail select { + position: absolute; + left: 110px; + outline: none; + border-radius: 3px; + width: 120px; + border-color: rgba(0, 0, 0, .3); + height: 30px; + font-size: 15px; + } + + .avatarDetail { + display: flex; + margin-top: 2px; + } + + .avatarDetail img { + border-radius: 2px; + width: 32px; + height: 32px; + flex-shrink: 0; + margin-right: 10px; + margin-top: 2px; + } + + .inputDetail input { + outline: none; + border-radius: 3px; + padding-left: 8px; + font-size: 15px; + width: 100%; + height: 34px; + border: 1px solid rgba(0, 0, 0, .3); + } + + .themeDetail { + display: flex; + width: 180px; + justify-content: space-between; + pointer-events: none; + } + + .themeDetail svg { + display: block; + } + + .themeDetail>div { + pointer-events: auto; + border-radius: 20px; + text-align: center; + padding: 8px 8px; + font-size: 14px; + cursor: pointer; + } + + .themeDetail>div * { + pointer-events: none; + } + + .themeDetail>div:hover { + background-color: var(--body-hover-bgcolor); + } + + .darkTheme>div:first-child { + background-color: var(--inputborder-color); + } + + .lightTheme>div:nth-child(2) { + background-color: var(--inputborder-color); + } + + .autoTheme>div:nth-child(3) { + background-color: var(--inputborder-color); + } + + .langDetail { + width: 110px; + } + + .enLang>div:first-child { + background-color: var(--inputborder-color); + } + + .zhLang>div:nth-child(2) { + background-color: var(--inputborder-color); + } + + #customAutoSet input { + width: 100px; + height: 30px; + line-height: 30px; + font-size: 15px; + outline: none; + border: 1px solid rgba(0, 0, 0, .3); + text-align: center; + border-radius: 3px; + background: #fff; + color: #24292f; + } + + #customAutoSet label { + margin-right: 8px; + } + + .progressBar { + position: relative; + width: 100%; + height: 12px; + border-radius: 6px; + background: #e0e0e0; + overflow: hidden; + } + + .nowProgress { + position: absolute; + left: 0; + top: 0; + height: 12px; + min-width: 1px; + border-radius: 6px; + background: #99c959; + } + + .progressDetail { + display: flex; + justify-content: space-between; + font-size: 15px; + } + + .cursorCls { + background-color: var(--inputborder-color); + width: 4px; + animation: 1s cursor-blinker infinite step-start; + } + + @keyframes cursor-blinker { + 0% { + opacity: 0; + } + + 50% { + opacity: 1; + } + + 100% { + opacity: 0; + } + } + + .apiSelsContainer { + position: relative; + z-index: 1; + } + + #apiSelect { + position: absolute; + top: 64px; + padding: 4px 0; + width: calc(100% - 20px); + left: 10px; + box-shadow: 0 0 6px rgba(0, 0, 0, 0.15); + max-height: 180px; + overflow-y: auto; + -webkit-user-select: none; + user-select: none; + } + + #apiSelect>div { + pointer-events: auto; + cursor: pointer; + font-size: 15px; + padding: 6px 0 6px 8px; + height: 36px; + display: flex; + justify-content: space-between; + align-items: center; + } + + #apiSelect>div:hover { + background-color:var(--body-hover-bgcolor); + } + + #apiSelect>div>span { + height: 100%; + line-height: 23px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + + #apiSelect>div * { + pointer-events: none; + } + + .delApiOption:hover { + background-color: var(--body-hover-bgcolor); + } + + .delApiOption { + width: 36px; + height: 36px; + flex-shrink: 0; + pointer-events: auto !important; + } + + .delApiOption>svg { + margin: 6px; + display: block; + } + + @keyframes textInOut { + from { + opacity: 0; + } + + to { + opacity: 1; + } + } + + .response[data-loading="true"]>.responseBody>*:not(.cursorCls) { + animation: textInOut 0.48s ease-in-out forwards; + } + + .cotBtn { + width: 120px; + height: 32px; + font-size: 14px; + user-select: none; + background-color: var(--inputborder-color); + position: relative; + padding-left: 14px; + line-height: 32px; + border-radius: 8px; + margin-bottom: 12px; + cursor: pointer; + } + + .cotBtn:hover { + background-color: var(--body-hover-bgcolor); + } + + .cotBtn.endCot { + width: 142px; + } + + .cotBtn::after { + content: ''; + display: inline-block; + position: absolute; + right: 15px; + top: 14px; + width: 6px; + height: 6px; + transform: rotate(-45deg); + } + + .cotBtn.down::after { + top: 11px; + transform: rotate(135deg); + } + + .cotContent { + background-color: var(--inputborder-color); + padding-left: 12px; + font-size: 14px; + } + + .cotContent>:last-child { + margin-bottom: 12px !important; + } + + .hide { + display: none; + } + + + + + + + diff --git a/luci-theme-footstrap/htdocs/luci-static/footstrap/logo_48.png b/luci-theme-footstrap/htdocs/luci-static/footstrap/logo_48.png new file mode 100644 index 00000000..ae7a6461 Binary files /dev/null and b/luci-theme-footstrap/htdocs/luci-static/footstrap/logo_48.png differ diff --git a/luci-theme-footstrap/htdocs/luci-static/footstrap/manifest.json b/luci-theme-footstrap/htdocs/luci-static/footstrap/manifest.json new file mode 100644 index 00000000..974777ad --- /dev/null +++ b/luci-theme-footstrap/htdocs/luci-static/footstrap/manifest.json @@ -0,0 +1,15 @@ +{ + "name": "LuCI", + "start_url": "/cgi-bin/luci/", + "display": "standalone", + "background_color": "#f6f8fa", + "theme_color": "#f6f8fa", + "icons": [ + { + "src": "app-icon-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "any maskable" + } + ] +} diff --git a/luci-theme-footstrap/htdocs/luci-static/resources/fs-appearance.js b/luci-theme-footstrap/htdocs/luci-static/resources/fs-appearance.js new file mode 100644 index 00000000..637bf893 --- /dev/null +++ b/luci-theme-footstrap/htdocs/luci-static/resources/fs-appearance.js @@ -0,0 +1,637 @@ +'use strict'; +'require baseclass'; +'require ui'; +'require fs-prefs as prefs'; +'require fs-axes as axes'; +'require fs-router as router'; +'require fs-assets as assets'; +'require fs-version as ver'; + +/* The Appearance controls: the DOM that presents the axes. It owns no preference: fs-prefs.js and + * fs-axes.js hold the axes and fs-version.js the version string. `view/footstrap/appearance.js` + * calls `renderStandalone()`, the whole surface this file ships. + * + * The version line makes no request and must not grow one: which version is installed is what this + * page answers, and which is available is the package manager's question. */ + +/* Colour readout. Two questions no stored value answers: what colour a role is right now (the + * palette's own while the axis is off) and what contrast it lands at. */ +let _probe = null; +function probeColor(expr) { + if (!_probe) { + /* Off-screen rather than display:none, so the reading does not depend on a display:none element + * computing `color` in every engine. Every declaration is !important: an app's CSS can style a + * plain
. */ + _probe = E('span', { 'id': 'fs-appearance-probe', 'aria-hidden': 'true' }); + _probe.style.cssText = 'position:fixed!important;left:-9999px!important;top:0!important;' + + 'width:0!important;height:0!important;overflow:hidden!important;' + + 'pointer-events:none!important;'; + document.body.appendChild(_probe); + } + /* cleared first: an expression the engine rejects leaves the previous colour standing, which + * would report a stale answer as a fresh one */ + _probe.style.setProperty('color', ''); + _probe.style.setProperty('color', expr, 'important'); + return getComputedStyle(_probe).color; +} + +/* A computed colour -> [r,g,b] 0..255, or null. Rasterised, not parsed: a computed `color` keeps the + * space it was authored in, so `oklch(0.54 0.19 300)` parses as three numbers in the wrong units + * (measured: #010078, graded "Too faint to read"). Painting one pixel makes the engine convert. */ +let _cx = null; +function rasterCtx() { + if (!_cx) { + const cv = document.createElement('canvas'); + cv.width = cv.height = 1; + _cx = cv.getContext('2d', { willReadFrequently: true }); + } + return _cx; +} +function parseColor(s) { + const cx = rasterCtx(); + /* fillStyle keeps the last value it could parse, so a colour this engine rejects would + * report the previous one as a fresh reading — the trap probeColor() clears for */ + cx.fillStyle = '#000'; + cx.fillStyle = String(s || ''); + cx.clearRect(0, 0, 1, 1); + cx.fillRect(0, 0, 1, 1); + const d = cx.getImageData(0, 0, 1, 1).data; + /* translucent: composite over nothing is meaningless for a readout */ + return d[3] === 255 ? [ d[0], d[1], d[2] ] : null; +} + +/* WCAG 2.x relative luminance and contrast ratio, on sRGB. Used only to report: the theme states + * what a colour costs and leaves the choice with the user, never correcting it (03-palettes.css + * derives the ink over a fill, which is a different question). */ +function luminance(rgb) { + const c = rgb.map((v) => { + const x = v / 255; + return (x <= .03928) ? (x / 12.92) : Math.pow((x + .055) / 1.055, 2.4); + }); + return (.2126 * c[0]) + (.7152 * c[1]) + (.0722 * c[2]); +} +function contrastRatio(fgExpr, bgExpr) { + const fg = parseColor(probeColor(fgExpr)), bg = parseColor(probeColor(bgExpr)); + if (!fg || !bg) return null; + const a = luminance(fg), b = luminance(bg); + return (Math.max(a, b) + .05) / (Math.min(a, b) + .05); +} + +function toHex(s) { + const rgb = parseColor(s) || [ 0, 0, 0 ]; + return '#' + rgb.map((v) => v.toString(16).padStart(2, '0')).join(''); +} + +/* One colour axis: a native swatch, a hex field and a button back to the palette's own colour. + * Reports through onPick as a hex string, or 0 for "back to the palette". */ +function colorControl(onPick, label, o) { + const swatch = E('input', { 'type': 'color', 'class': 'fs-color-swatch', 'aria-label': label }); + const field = E('input', { + 'type': 'text', 'class': 'fs-color-hex', 'spellcheck': 'false', 'autocomplete': 'off', + 'inputmode': 'text', 'maxlength': '7', 'aria-label': label + }); + const clear = E('button', { 'class': 'btn fs-color-clear', 'type': 'button' }, [ _('Palette', 'footstrap') ]); + const ratio = o.contrast ? E('div', { 'class': 'cbi-value-description fs-color-contrast' }) : null; + + let typed = false; + + /* Repaint everything that mirrors the axis. Called after every edit, and through the returned + * refresh() after a preset, palette switch or dark-mode flip — each changes what the palette's + * own colour is while this axis stays off. */ + function reflect(v) { + const live = probeColor(o.probe); + const hex = (typeof v === 'string') ? v : toHex(live); + swatch.value = hex; + /* do not fight the user mid-edit: `#0` is a legal prefix, and overwriting the field on + * every keystroke made the input impossible to type into */ + if (document.activeElement !== field) { field.value = hex; typed = false; } + /* the button back to the palette doubles as the axis state readout: enabled means the axis + * holds a colour of its own, disabled means the field shows the palette's */ + clear.disabled = !v; + if (!ratio) return; + const r = contrastRatio(o.contrast.fg, o.contrast.bg); + if (r === null) { ratio.hidden = true; ratio.removeAttribute('title'); return; } + /* The readout states what the ratio means; the number itself stays in the title. Thresholds are + * WCAG AA: 4.5:1 for body text, 3:1 for large text and for a UI shape. */ + const where = o.contrast.label; + const grade = (o.contrast.kind === 'shape') + ? ((r >= 3) + ? { cls: 'fs-contrast-aa', text: _('Clearly visible %s', 'footstrap').format(where) } + : { cls: 'fs-contrast-aa-large', text: _('Barely visible %s', 'footstrap').format(where) }) + : (r >= 4.5) + ? { cls: 'fs-contrast-aa', text: _('Easy to read %s', 'footstrap').format(where) } + : (r >= 3) + ? { cls: 'fs-contrast-aa-large', text: _('Hard to read %s — large text only', 'footstrap').format(where) } + : { cls: 'fs-contrast-low', text: _('Too faint to read %s', 'footstrap').format(where) }; + ratio.className = 'fs-color-contrast ' + grade.cls; + ratio.textContent = grade.text; + /* a pass says nothing: nine "easy to read" lines buried the one that was not */ + ratio.hidden = (grade.cls === 'fs-contrast-aa'); + ratio.title = _('Contrast %s:1 (WCAG AA wants %s:1 here)', 'footstrap') + .format(r.toFixed(1), (o.contrast.kind === 'shape') ? '3' : '4.5'); + } + + const pick = (v) => { onPick(v); reflect(v); }; + + swatch.addEventListener('input', () => pick(swatch.value.toLowerCase())); + /* commit on blur and Enter, not per keystroke: a half-typed `#0096` would repaint the page + * under the cursor. An unparseable value snaps back to what the axis holds, so the field + * cannot claim a colour the page is not painted in. An untouched field returns first, so a + * bare Tab-through never pins the shown palette colour on any of the 9 axes. */ + const commit = () => { + if (!typed) return; + const v = field.value.trim().toLowerCase(); + if ((/^#[0-9a-f]{6}$/).test(v)) pick(v); + /* re-read rather than a cached copy: the page can change what the axis holds behind this + * control (a preset, Reset to built-in), which a snapshot taken at build time would miss */ + else reflect(o.read()); + }; + field.addEventListener('input', () => { typed = true; }); + field.addEventListener('blur', commit); + field.addEventListener('keydown', (ev) => { if (ev.key === 'Enter') { ev.preventDefault(); commit(); } }); + clear.addEventListener('click', () => pick(0)); + + const wrap = E('div', { 'class': 'fs-colorctl' }, [ + E('div', { 'class': 'fs-color-row' }, [ swatch, field, clear ]) + ].concat(ratio ? [ ratio ] : [])); + /* the caller decides when this runs: probeColor() needs the document, and this control is not + * in it yet */ + wrap.fsRefresh = () => reflect(o.read()); + return wrap; +} + +/* Every stamp that moves a colour lands on :root, including head.ut's OS listener and an app + * rewriting data-darkmode, neither of which calls back here. */ +let _watch = null; +function unwatchColours() { + if (_watch) _watch.disconnect(); + _watch = null; +} +router.onNavigate(unwatchColours); +function watchColours(refresh) { + unwatchColours(); + _watch = new MutationObserver(refresh); + _watch.observe(document.documentElement, { + attributes: true, attributeFilter: [ 'data-darkmode', 'data-palette', 'data-wallpaper' ] + }); +} + +/* Build the page; renderStandalone() hands it over. Four tabs, most-used first: the colour scheme, + * the interface, the background with its glass and the colours nobody touches twice; Saving sits + * under them. Everything applies at once and is this browser's, in localStorage; only "Save to + * router" writes anything, pushing the current look to the router for other browsers. */ +function build() { + /* 24.10.0-24.10.2 have no ui.RangeSlider and a package from the feed still installs there (opkg does + * not version luci-base); one readable line beats the raw "not a constructor" TypeError. */ + if (!ui.RangeSlider) + return E('p', { 'class': 'alert-message warning' }, [ _('Footstrap needs OpenWrt 24.10.3 or newer.', 'footstrap') ]); + + /* every saved axis re-checks the Save button after applying, so it greys the moment this + * browser matches the saved default and un-greys when it diverges. Wrapped around the appliers + * because the controls call them directly and have no other seam back here. */ + const bump = (fn) => (v) => { fn(v); refreshSave(); }; + + /* Every colour control mirrors something it does not own: the palette's colour while its own + * axis is off, and the contrast that colour lands at. A palette switch or dark-mode flip moves + * all of it under controls nobody touched, so they refresh together. */ + const colourCtls = []; + const refreshColours = () => colourCtls.forEach((c) => c.fsRefresh()); + const repaint = (fn) => (v) => { fn(v); refreshColours(); }; + + /* One captioned row in LuCI's own shape: `.cbi-value` > `label.cbi-value-title` + + * `.cbi-value-field`, which base/30-forms.css and theme/60-inputs.css already lay out. `make` is + * handed the caption because each control needs it again as the aria-label of its inner + * input/select (a stock widget's wrapper div cannot carry a name). `o.desc` is a help line under + * the control, `o.extra` the rows with more than a control, `o.cls` marks the rows CSS singles out. */ + const group = (label, make, opts) => { + const o = opts || {}; + const desc = o.desc ? [ E('div', { 'class': 'cbi-value-description' }, [ o.desc ]) ] : []; + return E('div', { 'class': 'cbi-value' + (o.cls ? ' ' + o.cls : '') }, [ + E('label', { 'class': 'cbi-value-title' }, [ label ]), + E('div', { 'class': 'cbi-value-field' }, [ make(label) ].concat(desc, o.extra || [])) + ]); + }; + + /* the three literals every colour row repeats; a string literal survives minification intact */ + const CARD_BG = 'var(--fs-panel)', INK = 'var(--fs-text)', ON_CARD = _('on a card', 'footstrap'); + + /* A list of more than five, the palette, is a `ui.Select`; every number a `ui.RangeSlider`, already + * dressed by base/30-forms.css and theme/60-inputs.css (`ui.RangeSlider` reached openwrt-24.10 in + * 24.10.3). Listen for `widget-change`, the seam UIElement publishes, not an event on the inner + * element. RangeSlider also emits `widget-update` while the handle moves, which resizes the page + * under the drag; the appliers are idempotent, so wiring both costs nothing. */ + const selectCtl = (current, choices, apply, label) => { + /* an array of [value, label] pairs when a key is integer-like: Object.keys hoists it ahead of + * the authored order ('2020' rendered above Footstrap, the default); `sort` pins the order */ + const pairs = Array.isArray(choices) ? choices : Object.entries(choices); + const w = new ui.Select(String(current), Object.fromEntries(pairs), { widget: 'select', sort: pairs.map((p) => p[0]) }); + const node = w.render(); + node.querySelector('select, input').setAttribute('aria-label', label); + node.addEventListener('widget-change', () => apply(w.getValue())); + return node; + }; + + /* Two to three short choices are one row of pills, every choice in sight: native radios under one + * name, so the arrow keys move between them and a screen reader counts them; 80-appearance.css + * draws the pills. */ + let segSeq = 0; + const segCtl = (current, choices, apply, label) => { + const name = 'fs-ap-seg-' + (++segSeq); + const pairs = Array.isArray(choices) ? choices : Object.entries(choices); + return E('div', { 'class': 'fs-seg', 'role': 'radiogroup', 'aria-label': label }, pairs.map(([ v, text ]) => { + const input = E('input', { 'type': 'radio', 'name': name, 'value': v }); + input.checked = (String(current) === v); + input.addEventListener('change', () => { if (input.checked) apply(v); }); + return E('label', {}, [ input, E('span', {}, [ text ]) ]); + })); + }; + + const sliderCtl = (axis, apply, label, opts) => { + const o = opts || {}; + const w = new ui.RangeSlider(String(axis.current()), { + min: axis.min, max: axis.max, step: o.step || 1 + }); + const node = w.render(); + node.querySelector('select, input').setAttribute('aria-label', label); + const push = () => apply(parseInt(w.getValue(), 10)); + /* `live: false` for an axis whose value moves this control: Content width widens the column the + * page is drawn in, so applying mid-drag slides the handle from under the pointer and one small + * drag runs to the maximum. A keyboard user still applies at once (an arrow key fires `change`). */ + if (o.live !== false) node.addEventListener('widget-update', push); + node.addEventListener('widget-change', push); + return node; + }; + + const colourGroup = (label, axis, probe, contrast, opts = {}) => group(label, (lbl) => { + const ctl = colorControl(bump(axis.apply), lbl, { + probe: probe, + read: axis.current, + contrast: contrast + }); + colourCtls.push(ctl); + return ctl; + }, opts); + + /* a card is a stock `.cbi-section` under its own title; a tab pane is one whose title is its tab + * (ui.tabs reads data-tab-title); a subheading splits either in two */ + const card = (title, rows) => E('div', { 'class': 'cbi-section fs-ap-card' }, [ E('h3', {}, [ title ]) ].concat(rows)); + const pane = (name, title, rows) => E('div', { 'class': 'cbi-section fs-ap-card', 'data-tab': name, 'data-tab-title': title }, rows); + const sub = (title, cls) => E('div', { 'class': 'fs-ap-sub' + (cls ? ' ' + cls : '') }, [ E('h4', {}, [ title ]) ]); + + /* Every label here carries the 'footstrap' context: LuCI serves one merged catalogue and the first + * archive holding the hash wins, so a bare msgid is a global name any luci-app can win. */ + + /* ---- the colour scheme: what most visits come to change ---- */ + const scheme = pane('scheme', _('Colour scheme', 'footstrap'), [ + group(_('Mode', 'footstrap'), (label) => segCtl(prefs.currentMode(), [ + [ 'auto', _('Auto', 'footstrap') ], + [ 'light', _('Light', 'footstrap') ], + [ 'dark', _('Dark', 'footstrap') ] + ], bump(prefs.applyMode), label)), + + group(_('Palette', 'footstrap'), (label) => selectCtl(axes.palette.current(), [ + [ 'footstrap', 'Footstrap' ], + [ 'hicontrast', 'Hi-Contrast' ], + [ 'bootstrap', 'Bootstrap' ], + /* names another package, luci-theme-openwrt-2020, whose colourway this is */ + [ '2020', 'OpenWrt 2020' ], + [ 'forum', 'Forum' ] + ], bump(axes.palette.apply), label)), + + /* Accent recolours the accented controls (buttons, toggles, sliders, focus rings), not the + * canvas; any #rrggbb is taken. Read as text on a card, the use that fails first (as a fill it + * carries derived ink). */ + colourGroup(_('Accent', 'footstrap'), axes.accent, 'var(--fs-accent)', + { fg: 'var(--fs-accent)', bg: CARD_BG, label: ON_CARD }) + ]); + + /* ---- the interface ---- */ + const iface = pane('interface', _('Interface', 'footstrap'), [ + group(_('Layout', 'footstrap'), (label) => segCtl(prefs.currentLayout(), [ + [ 'sidebar', _('Sidebar', 'footstrap') ], + [ 'top', _('Top', 'footstrap') ] + ], bump(prefs.applyLayout), label)), + + /* always built and hidden by CSS in the top layout (:root[data-layout="top"] .fs-ap-submenus): + * the page is built once, so a currentLayout() branch would freeze it to the layout the page + * loaded in while CSS morphs the chrome live */ + group(_('Submenus', 'footstrap'), (label) => segCtl(prefs.currentAutoCollapse() ? 'on' : 'off', [ + [ 'off', _('Keep open', 'footstrap') ], + [ 'on', _('Auto-collapse', 'footstrap') ] + ], bump(prefs.applyAutoCollapse), label), { cls: 'fs-ap-submenus' }), + + group(_('Density', 'footstrap'), (label) => segCtl(axes.density.current(), [ + [ 'compact', _('Compact', 'footstrap') ], + [ 'normal', _('Normal', 'footstrap') ], + [ 'large', _('Large', 'footstrap') ] + ], bump(axes.density.apply), label)), + + /* the content column's own cap, separate from Density. The slider starts at the 1280px every + * page was designed against and only widens, which also keeps --fs-content-min (500px, what + * the sidebar-to-bar fold is measured against) out of reach by construction. */ + group(_('Content width', 'footstrap'), + (label) => sliderCtl(axes.contentWidth, bump(axes.contentWidth.apply), label, { step: 40, live: false })), + + group(_('Rounding', 'footstrap'), + (label) => sliderCtl(axes.radius, bump(axes.radius.apply), label)) + ]); + + /* ---- the background: the canvas, the wallpaper and the glass over it ---- + * The rows a value brings are siblings of the Wallpaper row: nested in its `.cbi-value-field` + * they started 216px right of every other control (measured on the router). They are hidden as + * a group, as stock pages do with a dependent field. The pills are the per-browser switch that + * keeps the Save button honest; Choose/Remove only swap the picture and never touch the axis. */ + const REMOVE = _('Remove', 'footstrap'); + const background = (() => { + const err = E('div', { 'class': 'fs-ap-err', 'role': 'alert', 'hidden': '' }); + const preview = E('img', { 'class': 'fs-ap-bgprev', 'alt': '', 'hidden': '' }); + const fileInput = E('input', { 'type': 'file', 'accept': 'image/*', 'style': 'display:none' }); + const chooseLabel = _('Choose image', 'footstrap'); + const chooseBtn = E('button', { 'class': 'btn cbi-button', 'type': 'button' }, [ chooseLabel ]); + const removeBtn = E('button', { 'class': 'btn cbi-button-remove', 'type': 'button', 'hidden': '' }, [ REMOVE ]); + + const patErr = E('div', { 'class': 'fs-ap-err', 'role': 'alert', 'hidden': '' }); + const patPreview = E('img', { 'class': 'fs-ap-bgprev', 'alt': '', 'hidden': '' }); + const patInput = E('input', { 'type': 'file', 'accept': 'image/svg+xml,.svg', 'style': 'display:none' }); + const patChooseLabel = _('Choose SVG', 'footstrap'); + const patChoose = E('button', { 'class': 'btn cbi-button', 'type': 'button' }, [ patChooseLabel ]); + const patRemove = E('button', { 'class': 'btn cbi-button-remove', 'type': 'button', 'hidden': '' }, [ REMOVE ]); + + /* Colours decides whether the file's own palette is kept or replaced by the theme's: a mask + * uses the alpha only, right for line art and wrong for artwork with its own colours. */ + const patRows = [ + group(_('Pattern', 'footstrap'), + () => E('div', { 'class': 'fs-ap-bgrow' }, [ patChoose, patRemove ]), + { extra: [ patInput, patPreview, patErr ] }), + group(_('Scale', 'footstrap'), (lbl) => sliderCtl(axes.patternSize, bump(axes.patternSize.apply), lbl, { step: 20 })), + group(_('Strength', 'footstrap'), (lbl) => sliderCtl(axes.patternStrength, bump(axes.patternStrength.apply), lbl, { step: 5 })), + group(_('Colours', 'footstrap'), (lbl) => segCtl(axes.patternInk.current(), [ + [ 'theme', _('Theme', 'footstrap') ], + [ 'original', _('As in file', 'footstrap') ] + ], bump(axes.patternInk.apply), lbl)) + ]; + /* Dim is an ordinary saved axis, so it is bump()-ed or the Save button misreports its status */ + const fileRows = [ + group(_('File', 'footstrap'), + () => E('div', { 'class': 'fs-ap-bgrow' }, [ chooseBtn, removeBtn ]), + { extra: [ fileInput, preview, err ] }), + group(_('Dim', 'footstrap'), (lbl) => sliderCtl(axes.photoDim, bump(axes.photoDim.apply), lbl, { step: 5 })) + ]; + + function reflect(tok) { + if (tok) { preview.src = axes.loginBg.url(tok); preview.hidden = false; removeBtn.hidden = false; } + else { preview.removeAttribute('src'); preview.hidden = true; removeBtn.hidden = true; } + } + function reflectPattern(tok) { + if (tok) { patPreview.src = axes.pattern.url(tok); patPreview.hidden = false; patRemove.hidden = false; } + else { patPreview.removeAttribute('src'); patPreview.hidden = true; patRemove.hidden = true; } + } + /* `hidden` on the row, which base/10-reset.css restates with !important (the UA's bare + * `[hidden]` loses to `.cbi-value`'s display). Hidden, not removed: each row holds a live + * control that a rebuild would freeze to its construction-time state. */ + function togglePanel(v) { + patRows.forEach((r) => { r.hidden = (v !== 'pattern'); }); + fileRows.forEach((r) => { r.hidden = (v !== 'file'); }); + } + reflect(axes.loginBg.current()); + reflectPattern(axes.pattern.current()); + togglePanel(axes.wallpaper.current()); + + const setWallpaper = (v) => { axes.wallpaper.apply(v); refreshSave(); togglePanel(v); }; + + /* Both uploads share one wiring (pick, upload, report, remove) and never move the Wallpaper + * pills, which the user may change mid-upload. The input is cleared on every change so + * re-picking the same file fires `change`; the label is restored in `finally`, or a failed + * upload leaves "Uploading…" standing. */ + const wireUploader = (u) => { + const fail = (e) => { u.err.textContent = String((e && e.message) || e); u.err.hidden = false; }; + u.choose.addEventListener('click', () => { u.err.hidden = true; u.input.click(); }); + u.input.addEventListener('change', () => { + const f = u.input.files && u.input.files[0]; + u.input.value = ''; + if (!f) return; + u.err.hidden = true; u.choose.disabled = true; + u.choose.textContent = _('Uploading…', 'footstrap'); + u.upload(f) + .then(u.reflect) + .catch(fail) + .finally(() => { u.choose.disabled = false; u.choose.textContent = u.label; }); + }); + u.remove.addEventListener('click', () => { + u.err.hidden = true; u.remove.disabled = true; + u.drop().then(() => u.reflect('')).catch(fail) + .finally(() => { u.remove.disabled = false; }); + }); + }; + + wireUploader({ + choose: patChoose, remove: patRemove, input: patInput, err: patErr, + label: patChooseLabel, reflect: reflectPattern, + upload: assets.uploadPattern, drop: assets.removePattern + }); + + wireUploader({ + choose: chooseBtn, remove: removeBtn, input: fileInput, err: err, + label: chooseLabel, reflect: reflect, + upload: assets.uploadLoginBg, drop: assets.removeLoginBg + }); + + /* The glass rows show only over a wallpaper (CSS: .fs-ap-glass), Material only while a + * surface is glass at all, Blur only for a material that blurs. An OS asking for less + * transparency or more contrast keeps the surfaces solid whatever the sliders say, and the + * page says so. */ + const glassRows = [ + sub(_('Transparency', 'footstrap'), 'fs-ap-glass'), + group(_('Card opacity', 'footstrap'), + (lbl) => sliderCtl(axes.cardOpacity, bump(axes.cardOpacity.apply), lbl, { step: 5 }), { + cls: 'fs-ap-glass', + desc: axes.glassBlocked() ? _('The system asks for less transparency or more contrast, so surfaces stay solid.', 'footstrap') : null + }), + group(_('Menu opacity', 'footstrap'), + (lbl) => sliderCtl(axes.barOpacity, bump(axes.barOpacity.apply), lbl, { step: 5 }), { cls: 'fs-ap-glass' }), + group(_('Material', 'footstrap'), (lbl) => segCtl(axes.material.current(), [ + [ 'flat', _('Flat', 'footstrap') ], + [ 'frosted', _('Frosted', 'footstrap') ], + [ 'acrylic', _('Acrylic', 'footstrap') ] + ], bump(axes.material.apply), lbl), { cls: 'fs-ap-glass fs-ap-material' }), + group(_('Blur', 'footstrap'), + (lbl) => sliderCtl(axes.blur, bump(axes.blur.apply), lbl, { step: 10 }), { cls: 'fs-ap-glass fs-ap-material fs-ap-blur' }) + ]; + + return pane('background', _('Background', 'footstrap'), [ + /* the canvas colour that tells routers apart; CSS hides it under a photo, which covers it */ + colourGroup(_('Tint', 'footstrap'), axes.tint, 'var(--fs-bg)', { + fg: 'var(--fs-text)', bg: 'var(--fs-bg)', label: _('on the canvas', 'footstrap') + }, { cls: 'fs-ap-tint', desc: _('Colours the page so you can tell routers apart.', 'footstrap') }), + + /* the strength half of the Tint, meaningful only in hue mode (a hex canvas has no chroma of + * ours to scale); CSS hides it otherwise. Not named "Density": the string is also the + * aria-label, so a screen reader would announce two rows under one name. */ + group(_('Tint strength', 'footstrap'), + (label) => sliderCtl(axes.tintStrength, bump(repaint(axes.tintStrength.apply)), label, { + step: 5 + }), { cls: 'fs-ap-tint fs-ap-tintstr' }), + + group(_('Wallpaper', 'footstrap'), (label) => segCtl(axes.wallpaper.current(), [ + [ 'off', _('Off', 'footstrap') ], + [ 'pattern', _('Pattern', 'footstrap') ], + [ 'file', _('File', 'footstrap') ] + ], setWallpaper, label)) + ].concat(patRows, fileRows, glassRows)); + })(); + + /* ---- the colours nobody touches twice: the last tab ---- + * The three status colours, then the surfaces: the sheet the UI is drawn on. Body text is read on + * each surface, so each reports --fs-text against itself; no ink is derived, since moving + * --fs-text would recolour what is measured against. The hairline takes the 3:1 UI-component + * threshold (a border is a shape). The rows are data because a string literal repeated eight + * times is not mangled by the minifier. */ + const colourRows = [ + ...[ + [ _('Good', 'footstrap'), axes.good, 'var(--fs-good)' ], + [ _('Warning', 'footstrap'), axes.warn, 'var(--fs-warn)' ], + [ _('Danger', 'footstrap'), axes.danger, 'var(--fs-danger)' ] + ].map(([ label, axis, ink ]) => + colourGroup(label, axis, ink, { fg: ink, bg: CARD_BG, label: ON_CARD })), + sub(_('Surfaces', 'footstrap')), + ...[ + [ _('Cards', 'footstrap'), axes.card, CARD_BG, INK, CARD_BG, ON_CARD ], + [ _('Controls', 'footstrap'), axes.control, 'var(--fs-panel2)', INK, 'var(--fs-panel2)', _('on a control', 'footstrap') ], + [ _('Sidebar and bar', 'footstrap'), axes.bar, 'var(--fs-bar-bg)', INK, 'var(--fs-bar-bg)', _('in the sidebar', 'footstrap') ], + [ _('Borders', 'footstrap'), axes.line, 'var(--fs-border)', 'var(--fs-border)', CARD_BG, ON_CARD, 'shape' ] + ].map(([ label, axis, probe, fg, bg, where, kind ]) => + colourGroup(label, axis, probe, { fg, bg, label: where, kind })) + ]; + + /* ---- saving: the router default and the version ---- + * Save writes the current look to /etc/config/footstrap over the scoped uci ACL. It does not + * change this browser (localStorage keeps overriding), so it only shows on a fresh browser. */ + /* each label is said twice in build(); hoisted, a msgid is paid once (24 B x2 -> 30 B) */ + const SAVE_TO_ROUTER = _('Save to router', 'footstrap'); + const RESET_TO_ROUTER = _('Reset to router', 'footstrap'); + const RESET_TO_BUILTIN = _('Reset to built-in', 'footstrap'); + const saveBtn = E('button', { 'class': 'btn cbi-button-action', 'type': 'button' }, [ SAVE_TO_ROUTER ]); + /* Two resets, because two things sit underneath a browser's tweaks: "Reset to router" clears them + * and lets every axis fall back to the router, "Reset to built-in" writes the theme's defaults + * explicitly. Neither touches /etc/config/footstrap. "Default" is not a label: it named the + * router's look in "Save as default" and the theme's in "Reset to default", three names for two + * states (forum topic 251930, post 92). */ + const resetSavedBtn = E('button', { 'class': 'btn', 'type': 'button' }, [ RESET_TO_ROUTER ]); + const resetBtn = E('button', { 'class': 'btn cbi-button-negative', 'type': 'button' }, [ RESET_TO_BUILTIN ]); + /* Save's only visible failure surface: the rpc rejecting (expired session 403, missing ACL, ubus + * down). A deleted config is not caught: commit no-ops and returns success (measured on the + * router); the package owns that file and the read side falls back to built-in defaults. */ + const saveErr = E('div', { 'class': 'fs-ap-err', 'role': 'alert', 'hidden': '' }); + + /* The Save button is the status: matching disables it, diverging enables it. A browser that + * refuses storage keeps its choices in fs-prefs's in-memory map, so the comparison stays honest; + * the note waits while a failed Save's error shows, or the .finally below would overwrite it. */ + function refreshSave() { + if (prefs.storageBroken() && saveErr.hidden) { + saveErr.textContent = _('This browser is not storing preferences (site data is blocked), so a change here lasts until you reload. Saving to the router still works and applies to every browser.', 'footstrap'); + saveErr.hidden = false; + } + const saved = axes.matchesSavedDefault(); + saveBtn.disabled = saved; + saveBtn.textContent = saved ? _('Saved to router', 'footstrap') : SAVE_TO_ROUTER; + } + saveBtn.addEventListener('click', () => { + saveBtn.disabled = true; + saveErr.hidden = true; + saveErr.title = ''; /* a stale rpc error would tooltip the storage note (measured: 'ubus code 6' over it) */ + axes.saveAsDefault() + /* on failure refreshSave re-enables the button so the user can retry; the usual cause + * is a stale session, which a reload fixes. The raw rpc error stays in a title + * tooltip. */ + .catch((e) => { + saveErr.textContent = _('Could not save to the router. Reload the page and try again.', 'footstrap'); + saveErr.title = String((e && e.message) || e); + saveErr.hidden = false; + }) + .finally(refreshSave); + }); + /* Two-click confirm on both: discarding local tweaks is destructive and a native confirm() is + * banned in this UI. Arming one disarms the other, so a primed button cannot be fired by a + * click meant for its neighbour. */ + let armed = null; + function disarm() { + if (!armed) return; + armed.btn.textContent = armed.label; + armed.btn.classList.remove('fs-ap-armed'); + armed = null; + } + function twoClick(btn, label, run) { + btn.addEventListener('click', () => { + if (armed && armed.btn === btn) { + disarm(); + run(); + location.reload(); + return; + } + disarm(); + armed = { btn: btn, label: label }; + btn.textContent = _('Confirm reset', 'footstrap'); + btn.classList.add('fs-ap-armed'); + }); + } + twoClick(resetSavedBtn, RESET_TO_ROUTER, axes.resetToSaved); + twoClick(resetBtn, RESET_TO_BUILTIN, axes.resetToBuiltin); + refreshSave(); /* correct label and enabled state before the first paint */ + + const versionLink = E('a', { + 'class': 'fs-ap-version', + 'href': ver.REPO_URL, + 'target': '_blank', + /* `noreferrer` alone: it implies noopener, and the theme's other outward links spell it + * that way */ + 'rel': 'noreferrer' + }, [ ver.label() ]); + + /* The theme's translations are their own packages, and nothing in a package manager can read `uci + * luci.main.lang` to fetch the right one. */ + const lang = (document.documentElement.getAttribute('lang') || '').trim(); + const untranslated = lang && lang !== 'en' && lang !== 'auto' && + _('Layout', 'footstrap') === 'Layout'; + const missing = untranslated ? E('div', { 'class': 'fs-ap-verrow fs-ap-i18n' }, [ + E('span', {}, [ _('This theme is not translated on this router yet.', 'footstrap') + ' ' ]), + E('code', {}, [ 'luci-i18n-footstrap-' + lang ]) + ]) : ''; + + const saving = card(_('Saving', 'footstrap'), [ + E('p', { 'class': 'fs-ap-lead' }, [ _('Footstrap theme settings apply at once and are stored permanently in this browser.', 'footstrap') ]), + E('div', { 'class': 'fs-ap-actrow' }, [ saveBtn, E('span', { 'class': 'fs-ap-actgap', 'aria-hidden': 'true' }), resetSavedBtn, resetBtn ]), + saveErr, + E('ul', { 'class': 'fs-ap-help' }, [ + E('li', {}, [ _('Save to router — store this look on the router. Browsers with a look of their own keep it.', 'footstrap') ]), + E('li', {}, [ _('Reset to router — drop this browser\'s changes and follow the router.', 'footstrap') ]), + E('li', {}, [ _('Reset to built-in — go back to the look the theme ships with.', 'footstrap') ]) + ]), + E('div', { 'class': 'fs-ap-footer' }, [ + E('div', { 'class': 'fs-ap-verrow' }, [ versionLink ]), + missing + ]) + ]); + + /* LuCI's own tab group, as on every tabbed form: it puts the strip before the panes and keeps + * the open tab for the session. A pane shown again re-reads its colours, which a palette or + * dark-mode change may have moved while it was hidden. */ + const panes = E('div', { 'class': 'fs-ap-panes' }, [ + scheme, + iface, + background, + pane('colours', _('Colours', 'footstrap'), colourRows) + ]); + const page = E('div', { 'class': 'fs-ap' }, [ panes, saving ]); + ui.tabs.initTabGroup(panes.childNodes); + panes.addEventListener('cbi-tab-active', refreshColours); + + Promise.resolve().then(refreshColours); + + watchColours(refreshColours); + return page; +} + +return baseclass.extend({ + /* the dispatched page's whole render(), called by view/footstrap/appearance.js */ + renderStandalone: () => Promise.resolve().then(build), +}); diff --git a/luci-theme-footstrap/htdocs/luci-static/resources/fs-assets.js b/luci-theme-footstrap/htdocs/luci-static/resources/fs-assets.js new file mode 100644 index 00000000..988c07e6 --- /dev/null +++ b/luci-theme-footstrap/htdocs/luci-static/resources/fs-assets.js @@ -0,0 +1,291 @@ +'use strict'; +'require baseclass'; +'require rpc'; +'require ui'; +'require fs-axes as axes'; + +/* fs-assets — putting a file on the router and taking it off again: the pattern and login-photo uploads, + * the SVG sanitizer, the rollback. A module of its own: only the Appearance page needs these ~4 KB. */ + +/* Hoisted: a string literal is not mangled, so each repeat is paid on flash; msgid stays literal + * for update-po.sh. */ +const MSG_UPLOAD_FAILED = _('Upload failed.', 'footstrap'); +const MSG_NOT_SVG = _('That file is not an SVG image.', 'footstrap'); +const MSG_BAD_IMAGE = _('Could not process the image.', 'footstrap'); +const MSG_PICK_SVG = _('Please choose an SVG file.', 'footstrap'); +/* One template, two numbers: a translator places both %d in one sentence instead of agreeing + * plurals. */ +const MSG_SANITIZED = _('Cleaned before upload: removed %d unsafe element(s) and %d external reference(s).', 'footstrap'); +const MSG_NOTHING_LEFT = _('That SVG had nothing left once the unsafe parts were removed.', 'footstrap'); + +/* ---- the pattern: an SVG the admin uploads, tiled and recoloured ---- + * Fixed path matched by the rpcd ACL, under /etc so an upgrade keeps it. Not the security boundary: a + * `curl` to cgi-upload skips every check below; a direct open is stopped by the CGI's + * `Content-Security-Policy: default-src 'none'; sandbox`. */ +const PAT_PATH = '/etc/footstrap/pattern.svg'; /* cgi-upload target; the ACL grants exactly this */ +const PAT_MAX = 512 * 1024; /* a tile that has to reach a router's flash and then every page load */ +/* What `_sanitizeSvg` removes, judged on the parsed document, not the text. Painted as an image nothing + * loads; these rules cover a direct open of the file: + * - elements: only the SVG namespace stays (`` ran same-origin), minus + * script, foreignObject, a ` + {% endif %} + + + {# + Lets an admin install this page as an app (icon, own window, no address bar), worth more + than a tab on the phone LuCI is opened from when the network is broken. Both files are + static, a theme having no dispatcher node to render them per request: start_url is + /cgi-bin/luci/, uhttpd's default (a moved LuCI opens the standard path), and + background/theme_color is the default palette's page colour, read once at install time. + .json, not .webmanifest: uhttpd's compiled-in MIME map has no entry for that extension, + so it answers application/octet-stream. + #} + + {# + iOS reads this link and never the manifest's icons array, and it scales the 192 square the + manifest also uses down to its own 180. One raster serves both. + #} + + {% if (dispatched?.css): %} + {# + The `css` property of the dispatched menu.d node: a stylesheet the server links for the + page instead of the view injecting one at module eval. `dispatched`, not `node`: the + dispatcher exports the resolved node as `dispatched` and never binds `node`, so no ucode + theme emitted this link until openwrt/luci#8920. That commit also adds `css: 'string'` to + the node schema; without it the property is filtered out and this stays false, as on 24.10 + and 25.12. Versioned, as uhttpd sends no Cache-Control. Not `data-fs-shell`: the sheet + belongs to one page, so the SPA router must judge and contain it like any app sheet. + #} + + {% endif %} + {% if (css): %} + {# + no `title` attribute: on a + {% endif %} + {# + The catalogue is a CGI spawn on the critical path (30 ms on the router) that delivers 13 + bytes at lang=en, where no en catalogue exists because the msgids already are English. + The process is the cost, so emit those bytes inline when the language has no catalogue + and keep the tag when it has. The probe mirrors the server: load_catalog() reads every + `*..lmo` in /usr/lib/lua/luci/i18n, so a router shipping an en catalogue still gets + the tag; deciding by language NAME would drop it. Fails open: a throwing probe keeps the + tag, as a missing catalogue renders English and reports nothing. + #} + {%- let has_catalog = true; + try { has_catalog = length(require('fs').glob(`/usr/lib/lua/luci/i18n/*.${dispatcher.lang}.lmo`)) > 0; } catch (e) {} %} + {% if (has_catalog): %} + + {% else %} + + {% endif %} + {# + the one asset stock LuCI leaves without a cache key. Must stay synchronous and un-deferred: + luci.js captures `window.cbi_init` in its constructor, and `defer` is not an option for the + catalogue above either — footer.ut runs `L.require('menu-footstrap')` inline while the + parser is still running, so a module's `_()` would race a deferred window.TR. + #} + + diff --git a/luci-theme-footstrap/ucode/template/themes/footstrap/partials/notice.ut b/luci-theme-footstrap/ucode/template/themes/footstrap/partials/notice.ut new file mode 100644 index 00000000..c6227eab --- /dev/null +++ b/luci-theme-footstrap/ucode/template/themes/footstrap/partials/notice.ut @@ -0,0 +1,16 @@ +{# + One top-of-content warning notice: heading, sentence and an optional "go fix it" button. The + `dispatcher.lookup` guard must not drift: `path` is looked up before it is linked, so a build + without that page, or a user without rights to it, gets the warning and no dead link. + include(…, { title, text }) for a plain notice; add { path, label } for the button. + title/text are emitted unescaped: they are `_()` results over strings this theme ships, not + request data. + Licensed to the public under the Apache License 2.0. +-#} +
+

{{ title }}

+

{{ text }}

+ {% if (path && dispatcher.lookup(path)): %} + + {% endif %} +
diff --git a/luci-theme-footstrap/ucode/template/themes/footstrap/sysauth.ut b/luci-theme-footstrap/ucode/template/themes/footstrap/sysauth.ut new file mode 100644 index 00000000..429ea5e5 --- /dev/null +++ b/luci-theme-footstrap/ucode/template/themes/footstrap/sysauth.ut @@ -0,0 +1,185 @@ +{# + The LOGIN page. + + Copyright 2008 Steven Barth + Copyright 2008-2012 Jo-Philipp Wich + Licensed to the public under the Apache License 2.0. + + It exists because the generic template includes the header WITHOUT `blank_page: true`: with no + theme-local sysauth the whole chrome renders around the login form with every control dead. + No departure here may drop a field the backend posts. + Do not re-introduce a `