diff --git a/scripts/core/clashctl.sh b/scripts/core/clashctl.sh index 4d14724..8dd31bc 100644 --- a/scripts/core/clashctl.sh +++ b/scripts/core/clashctl.sh @@ -5437,11 +5437,129 @@ tun_log_tun_adapter_line() { grep -E '\[TUN\].*Tun adapter|Tun adapter listening' "$log_file" 2>/dev/null | tail -n 1 } +tun_log_tun_adapter_cidrs() { + local adapter_line + + adapter_line="$(tun_log_tun_adapter_line 2>/dev/null || true)" + [ -n "${adapter_line:-}" ] || return 1 + + printf '%s\n' "$adapter_line" | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}/[0-9]{1,2}' 2>/dev/null +} + +tun_ipv4_to_int() { + local ip="$1" + local a b c d octet n value + + IFS=. read -r a b c d </dev/null)" || return 1 + base_value="$(tun_ipv4_to_int "$base" 2>/dev/null)" || return 1 + + if [ "$prefix" -eq 0 ]; then + mask=0 + else + mask=$(((0xffffffff << (32 - prefix)) & 0xffffffff)) + fi + + [ $((ip_value & mask)) -eq $((base_value & mask)) ] +} + +tun_ipv4_in_default_tun_range() { + case "$1" in + 28.*|198.18.*|198.19.*) + return 0 + ;; + *) + return 1 + ;; + esac +} + +tun_log_source_ip_is_tun() { + local ip="$1" + local cidrs="${2:-}" + local cidr + + tun_ipv4_in_default_tun_range "$ip" && return 0 + + [ -n "${cidrs:-}" ] || cidrs="$(tun_log_tun_adapter_cidrs 2>/dev/null || true)" + [ -n "${cidrs:-}" ] || return 1 + + while IFS= read -r cidr; do + [ -n "${cidr:-}" ] || continue + tun_ipv4_in_cidr "$ip" "$cidr" && return 0 + done <.*/\2/p' \ + | tail -n 1 +} + tun_log_tun_source_line() { local log_file="$LOG_DIR/mihomo.out.log" - [ -f "$log_file" ] || return 1 + local adapter_cidrs line source_ip matched_line - grep -E '(^|[^0-9])28\.0\.0\.[0-9]+:[0-9]+.*-->' "$log_file" 2>/dev/null | tail -n 1 + [ -f "$log_file" ] || return 1 + adapter_cidrs="$(tun_log_tun_adapter_cidrs 2>/dev/null || true)" + + while IFS= read -r line; do + case "$line" in + *'-->'*) ;; + *) continue ;; + esac + + source_ip="$(printf '%s\n' "$line" | tun_log_line_source_ip 2>/dev/null || true)" + [ -n "${source_ip:-}" ] || continue + + if tun_log_source_ip_is_tun "$source_ip" "$adapter_cidrs"; then + matched_line="$line" + fi + done < "$log_file" + + [ -n "${matched_line:-}" ] || return 1 + printf '%s\n' "$matched_line" } tun_log_has_tun_traffic_evidence() { @@ -5504,7 +5622,7 @@ tun_doctor_log_evidence() { if [ -n "${traffic_line:-}" ]; then echo " 🐱 Tun 流量日志:$traffic_line" else - echo " 🚨 Tun 流量日志:未发现 28.0.0.x Tun 源地址流量" + echo " 🚨 Tun 流量日志:未发现明确 Tun 源地址流量" fi } diff --git a/scripts/dev/check-tun-log-detection.sh b/scripts/dev/check-tun-log-detection.sh new file mode 100644 index 0000000..7dadb9b --- /dev/null +++ b/scripts/dev/check-tun-log-detection.sh @@ -0,0 +1,73 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)" + +source_clashctl_for_tests() { + set -- "" + # Source the real functions; suppress the no-arg usage printed by the command dispatcher. + source "$PROJECT_DIR/scripts/core/clashctl.sh" >/dev/null +} + +source_clashctl_for_tests + +tmp_dir="$(mktemp -d)" +trap 'rm -rf "$tmp_dir"' EXIT + +mkdir -p "$tmp_dir/logs" +LOG_DIR="$tmp_dir/logs" + +run_case() { + local name="$1" + local adapter_line="$2" + local traffic_line="$3" + local expected="$4" + local result + + printf '%s\n%s\n' "$adapter_line" "$traffic_line" > "$LOG_DIR/mihomo.out.log" + + if tun_log_tun_source_line > "$tmp_dir/out"; then + result="pass" + else + result="fail" + fi + + if [ "$result" != "$expected" ]; then + echo "not ok - $name: got $result, expected $expected" >&2 + [ -s "$tmp_dir/out" ] && sed 's/^/ /' "$tmp_dir/out" >&2 + return 1 + fi + + echo "ok - $name" +} + +run_case \ + "detects 198.18.0.x tun traffic" \ + "[TUN] Tun adapter listening at: Meta([198.18.0.1/30],[])" \ + "[TCP] 198.18.0.2:43820 --> example.com:443 match RuleSet" \ + "pass" + +run_case \ + "detects 198.18.0.x tun traffic without arrow spacing" \ + "[TUN] Tun adapter listening at: Meta([198.18.0.1/30],[])" \ + "[TCP] 198.18.0.2:43820-->example.com:443 match RuleSet" \ + "pass" + +run_case \ + "detects 28.0.0.0/8 tun traffic" \ + "[TUN] Tun adapter listening at: Meta([28.0.0.1/30],[])" \ + "[TCP] 28.3.4.5:43820 --> example.com:443 match RuleSet" \ + "pass" + +run_case \ + "detects traffic from parsed adapter cidr" \ + "[TUN] Tun adapter listening at: Meta([172.31.9.1/30],[])" \ + "[TCP] 172.31.9.2:43820 --> example.com:443 match RuleSet" \ + "pass" + +run_case \ + "ignores unrelated source traffic" \ + "[TUN] Tun adapter listening at: Meta([198.18.0.1/30],[])" \ + "[TCP] 10.0.0.2:43820 --> example.com:443 match RuleSet" \ + "fail"