Detect Tun traffic across default and parsed CIDR ranges

Generalize Tun source-IP detection in doctor log evidence: parse the
TUN adapter CIDR from logs and match traffic against it, while keeping
built-in default Tun ranges (28.x, 198.18.x, 198.19.x). Add offline
check script covering the new detection cases.
This commit is contained in:
Arvin
2026-06-16 16:15:48 +08:00
parent 83e8259edc
commit a13da0e565
2 changed files with 194 additions and 3 deletions
+121 -3
View File
@@ -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 <<EOF
$ip
EOF
[ -n "${a:-}" ] && [ -n "${b:-}" ] && [ -n "${c:-}" ] && [ -n "${d:-}" ] || return 1
value=0
for octet in "$a" "$b" "$c" "$d"; do
case "$octet" in
''|*[!0-9]*)
return 1
;;
esac
n=$((10#$octet))
[ "$n" -ge 0 ] && [ "$n" -le 255 ] || return 1
value=$(((value << 8) + n))
done
printf '%s\n' "$value"
}
tun_ipv4_in_cidr() {
local ip="$1"
local cidr="$2"
local base prefix ip_value base_value mask
base="${cidr%/*}"
prefix="${cidr#*/}"
case "$prefix" in
''|*[!0-9]*)
return 1
;;
esac
prefix=$((10#$prefix))
[ "$prefix" -ge 0 ] && [ "$prefix" -le 32 ] || return 1
ip_value="$(tun_ipv4_to_int "$ip" 2>/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 <<EOF
$cidrs
EOF
return 1
}
tun_log_line_source_ip() {
sed -nE 's/.*(^|[^0-9])(([0-9]{1,3}\.){3}[0-9]{1,3}):[0-9]+.*-->.*/\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
}
+73
View File
@@ -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"