clean commit

This commit is contained in:
sushen339
2025-10-17 02:14:17 +08:00
commit ca016e8fca
6 changed files with 2342 additions and 0 deletions
+346
View File
@@ -0,0 +1,346 @@
#!/bin/sh
# TPROXY 透明代理脚本 for OpenWrt with NFTables
# 功能: IPv4/v6, DNS劫持(可选), IP分流, 本机代理
# 版本: 1.1.2 (2025-06-16)
# 维护: su
# --- [ 用户配置区: 请根据您的环境修改此区域 ] ---
# --- 功能开关 ---
# 是否开启DNS劫持功能. "true":开启, "false":关闭
ENABLE_DNS_HIJACKING="false"
# --- 核心设置 ---
# TPROXY 代理服务监听的 TCP/UDP 端口
PROXY_PORT=7893
# Adguard 或其他 DNS 软件的监听端口
ADGUARDHOME_DNS_PORT=553
# 代理软件的 DNS 监听端口 (仅在开启劫持时有效)
PROXY_DNS_PORT=1053
# --- 系统集成设置 ---
# LAN 接口名称
LAN_IF="br-lan"
# 运行代理软件的用户名 (用于豁免代理自身流量,防止死循环)
PROXY_USER="root"
# 存放 .nft 自定义规则文件的目录
NFT_CUSTOM_PATH="/etc/nikki/nftables"
# 您正在使用的 nftables 表名
NFT_TABLE="nikki"
# --- IP 集合名称 (需与 .nft 文件中定义的名称完全一致) ---
NFT_SET_RESERVED_V4="reserved_ip"
NFT_SET_RESERVED_V6="reserved_ip6"
NFT_SET_BYPASS_V4="china_ip"
NFT_SET_BYPASS_V6="china_ip6"
# --- 高级设置 ---
# 防火墙标记 (fwmark) 和策略路由表 ID
PROXY_MARK=0xff
ROUTE_TABLE=100
# 锁文件路径 (防止重复运行)
LOCK_FILE="/var/run/tproxy.lock"
# --- [ 核心逻辑区: 通常无需修改 ] ---
check_root() {
if [ "$(id -u)" -ne 0 ]; then
printf "错误: 此脚本需要以 root 权限运行。\n" >&2
exit 1
fi
}
# 检查是否已有实例运行
check_lock() {
if [ -f "$LOCK_FILE" ]; then
# 检查服务是否真正在运行(通过防火墙表存在性判断)
if nft list table inet "$NFT_TABLE" >/dev/null 2>&1; then
printf "错误: TPROXY 服务已在运行中\n" >&2
printf "如需重新启动,请使用: %s restart\n" "$0" >&2
printf "如需强制启动,请先停止服务: %s stop\n" "$0" >&2
exit 1
else
# 清理失效的锁文件
printf " -> 清理失效的锁文件...\n"
rm -f "$LOCK_FILE"
fi
fi
}
# 创建锁文件
create_lock() {
# 确保锁文件目录存在
local lock_dir=$(dirname "$LOCK_FILE")
[ ! -d "$lock_dir" ] && mkdir -p "$lock_dir" 2>/dev/null
# 写入启动时间戳而不是PID,因为脚本执行完后进程会结束
date '+%Y-%m-%d %H:%M:%S' > "$LOCK_FILE"
if [ $? -ne 0 ]; then
printf "警告: 无法创建锁文件 %s\n" "$LOCK_FILE" >&2
else
printf " -> 锁文件已创建: %s\n" "$LOCK_FILE"
fi
}
# 清理锁文件
remove_lock() {
if [ -f "$LOCK_FILE" ]; then
rm -f "$LOCK_FILE"
printf " -> 锁文件已清理: %s\n" "$LOCK_FILE"
fi
}
load_user_nft_files() {
if [ ! -d "$NFT_CUSTOM_PATH" ]; then
printf "错误: 找不到目录 %s\n" "$NFT_CUSTOM_PATH" >&2
exit 1
fi
printf " -> 正在从 %s 加载自定义规则...\n" "$NFT_CUSTOM_PATH"
# 检查是否存在 .nft 文件
file_count=$(find "$NFT_CUSTOM_PATH" -name "*.nft" -type f | wc -l)
if [ "$file_count" -eq 0 ]; then
printf "警告: 目录 %s 中没有找到 .nft 文件\n" "$NFT_CUSTOM_PATH" >&2
return 0
fi
for file in "$NFT_CUSTOM_PATH"/*.nft; do
if [ -f "$file" ]; then
printf " - 加载 %s\n" "$(basename "$file")"
if ! nft -f "$file"; then
printf "错误: 加载 %s 时发生错误!\n" "$file" >&2
exit 1
fi
fi
done
}
do_start() {
printf "▶️ 启动透明代理服务...\n"
check_lock
# 先清理可能存在的残留规则,但不删除锁文件
printf " -> 清理可能存在的残留规则...\n"
ip -4 rule del fwmark $PROXY_MARK lookup $ROUTE_TABLE 2>/dev/null || true
ip -6 rule del fwmark $PROXY_MARK lookup $ROUTE_TABLE 2>/dev/null || true
ip -4 route flush table $ROUTE_TABLE 2>/dev/null
ip -6 route flush table $ROUTE_TABLE 2>/dev/null
nft delete table inet $NFT_TABLE 2>/dev/null || true
# 创建锁文件
create_lock
# 设置异常退出时自动清理锁文件
trap 'remove_lock; exit' INT TERM
# 1. 设置策略路由
printf " -> 正在设置策略路由...\n"
ip -4 rule add fwmark $PROXY_MARK lookup $ROUTE_TABLE
ip -6 rule add fwmark $PROXY_MARK lookup $ROUTE_TABLE
ip -4 route add local default dev lo table $ROUTE_TABLE
ip -6 route add local default dev lo table $ROUTE_TABLE
# 2. 加载并配置 nftables
printf " -> 正在加载并配置 nftables...\n"
nft add table inet $NFT_TABLE
if [ $? -ne 0 ]; then
printf "错误: 创建 nftables 表 '%s' 失败。请检查是否已有同名表或nftables服务是否正常。\n" "$NFT_TABLE" >&2
exit 1
fi
# 加载 IP Set 文件
load_user_nft_files
nft add chain inet $NFT_TABLE mangle_prerouting { type filter hook prerouting priority -150\; }; nft flush chain inet $NFT_TABLE mangle_prerouting
nft add chain inet $NFT_TABLE mangle_output { type route hook output priority -150\; }; nft flush chain inet $NFT_TABLE mangle_output
for chain in tproxy_lan_v4 tproxy_lan_v6 tproxy_loc_v4 tproxy_loc_v6; do
nft add chain inet $NFT_TABLE "$chain"; nft flush chain inet $NFT_TABLE "$chain"
done
# 3. 应用规则
printf " -> 正在应用核心防火墙规则...\n"
# 规则 A: DNS 劫持 (可选)
if [ "$ENABLE_DNS_HIJACKING" = "true" ]; then
printf " - DNS 劫持已开启。\n"
nft add chain inet $NFT_TABLE dns_redirect { type nat hook prerouting priority -100\; }; nft flush chain inet $NFT_TABLE dns_redirect
nft add rule inet $NFT_TABLE dns_redirect iifname $LAN_IF meta l4proto {tcp, udp} th dport 53 redirect to :$PROXY_DNS_PORT
else
printf " - DNS 劫持已关闭。\n"
fi
# 规则 B: 处理本机发出流量 (在 OUTPUT 链中仅作标记)
nft add rule inet $NFT_TABLE mangle_output meta nfproto ipv4 jump tproxy_loc_v4
nft add rule inet $NFT_TABLE mangle_output meta nfproto ipv6 jump tproxy_loc_v6
nft add rule inet $NFT_TABLE tproxy_loc_v4 skuid $PROXY_USER return # 关键: 豁免代理程序自身,防止死循环
nft add rule inet $NFT_TABLE tproxy_loc_v4 ip daddr @$NFT_SET_RESERVED_V4 return
nft add rule inet $NFT_TABLE tproxy_loc_v4 ip daddr @$NFT_SET_BYPASS_V4 return
# 不处理 DNS 流量
nft add rule inet $NFT_TABLE tproxy_loc_v4 meta l4proto {tcp, udp} th dport {53, $ADGUARDHOME_DNS_PORT, $PROXY_DNS_PORT} return
nft add rule inet $NFT_TABLE tproxy_loc_v4 meta l4proto {tcp, udp} meta mark set $PROXY_MARK
nft add rule inet $NFT_TABLE tproxy_loc_v6 skuid $PROXY_USER return
nft add rule inet $NFT_TABLE tproxy_loc_v6 ip6 daddr @$NFT_SET_RESERVED_V6 return
nft add rule inet $NFT_TABLE tproxy_loc_v6 ip6 daddr @$NFT_SET_BYPASS_V6 return
# 不处理 DNS 流量
nft add rule inet $NFT_TABLE tproxy_loc_v6 meta l4proto {tcp, udp} th dport {53, $ADGUARDHOME_DNS_PORT, $PROXY_DNS_PORT} return
nft add rule inet $NFT_TABLE tproxy_loc_v6 meta l4proto {tcp, udp} meta mark set $PROXY_MARK
# 规则 C: 处理局域网及本机回环流量 (在 PREROUTING 链中执行 TPROXY)
nft add rule inet $NFT_TABLE mangle_prerouting iifname $LAN_IF meta nfproto ipv4 meta l4proto {tcp, udp} jump tproxy_lan_v4
nft add rule inet $NFT_TABLE mangle_prerouting iifname $LAN_IF meta nfproto ipv6 meta l4proto {tcp, udp} jump tproxy_lan_v6
nft add rule inet $NFT_TABLE mangle_prerouting iifname "lo" meta nfproto ipv4 meta l4proto {tcp, udp} meta mark $PROXY_MARK jump tproxy_lan_v4
nft add rule inet $NFT_TABLE mangle_prerouting iifname "lo" meta nfproto ipv6 meta l4proto {tcp, udp} meta mark $PROXY_MARK jump tproxy_lan_v6
nft add rule inet $NFT_TABLE tproxy_lan_v4 ip daddr @$NFT_SET_RESERVED_V4 return
nft add rule inet $NFT_TABLE tproxy_lan_v4 ip daddr @$NFT_SET_BYPASS_V4 return
# 不处理 DNS 流量
nft add rule inet $NFT_TABLE tproxy_lan_v4 meta l4proto {tcp, udp} th dport {53, $ADGUARDHOME_DNS_PORT, $PROXY_DNS_PORT} return
nft add rule inet $NFT_TABLE tproxy_lan_v4 meta l4proto {tcp, udp} meta mark set $PROXY_MARK tproxy to :$PROXY_PORT
nft add rule inet $NFT_TABLE tproxy_lan_v6 ip6 daddr @$NFT_SET_RESERVED_V6 return
nft add rule inet $NFT_TABLE tproxy_lan_v6 ip6 daddr @$NFT_SET_BYPASS_V6 return
# 不处理 DNS 流量
nft add rule inet $NFT_TABLE tproxy_lan_v6 meta l4proto {tcp, udp} th dport {53, $ADGUARDHOME_DNS_PORT, $PROXY_DNS_PORT} return
nft add rule inet $NFT_TABLE tproxy_lan_v6 meta l4proto {tcp, udp} meta mark set $PROXY_MARK tproxy to :$PROXY_PORT
printf "✅ 透明代理服务已成功启动。\n"
# 清除陷阱,正常启动时保持锁文件
trap - INT TERM
}
do_stop() {
printf "⏹️ 停止透明代理服务...\n"
# 清理策略路由
ip -4 rule del fwmark $PROXY_MARK lookup $ROUTE_TABLE 2>/dev/null || true
ip -6 rule del fwmark $PROXY_MARK lookup $ROUTE_TABLE 2>/dev/null || true
# 清理自定义路由表内容
ip -4 route flush table $ROUTE_TABLE 2>/dev/null
ip -6 route flush table $ROUTE_TABLE 2>/dev/null
# 删除 nft 路由表
printf " -> 正在清理防火墙表 '%s'...\n" "$NFT_TABLE"
nft delete table inet $NFT_TABLE 2>/dev/null || true
# 清理锁文件
remove_lock
printf "🛑 透明代理服务已完全停止。\n"
}
do_status() {
ICON_OK="✅"
ICON_FAIL="❌"
ICON_INFO="️"
ICON_OFF="⚪️"
printf "--- TPROXY 服务状态检查 ---\n"
# 检查锁文件状态
if [ -f "$LOCK_FILE" ]; then
local start_time=$(cat "$LOCK_FILE" 2>/dev/null)
if [ -n "$start_time" ]; then
printf "锁文件状态: %s 存在 (启动时间: %s)\n" "$ICON_OK" "$start_time"
else
printf "锁文件状态: %s 存在但无效\n" "$ICON_FAIL"
fi
else
printf "锁文件状态: %s 不存在\n" "$ICON_OFF"
fi
if ! nft list table inet "$NFT_TABLE" >/dev/null 2>&1; then
printf "总状态: %s 未激活 (找不到防火墙表 '%s')\n" "$ICON_OFF" "$NFT_TABLE"
exit 0
fi
if ip rule show | grep -q "fwmark $PROXY_MARK lookup $ROUTE_TABLE"; then
printf "总状态: %s 激活\n" "$ICON_OK"
else
printf "总状态: %s 部分激活 (防火墙规则存在,但策略路由缺失)\n" "$ICON_FAIL"
fi
printf "\n[+] IP 路由与规则:\n"
if ip rule show | grep -q "fwmark $PROXY_MARK"; then
printf " %s 策略路由:\n" "$ICON_INFO"
ip rule show | grep "fwmark $PROXY_MARK" | sed 's/^/ -> /'
else
printf " %s 策略路由: %s 缺失!\n" "$ICON_FAIL" "$ICON_FAIL"
fi
printf " %s 自定义路由表 (ID: %s):\n" "$ICON_INFO" "$ROUTE_TABLE"
ip route show table "$ROUTE_TABLE" | sed 's/^/ -> /'
printf "\n[+] NFTables 规则状态 (主表: %s)\n" "$NFT_TABLE"
if [ "$ENABLE_DNS_HIJACKING" = "true" ]; then
if nft list chain inet "$NFT_TABLE" "dns_redirect" >/dev/null 2>&1; then
printf " %s DNS 劫持: 已激活 (转发至端口 %s)\n" "$ICON_OK" "$PROXY_DNS_PORT"
else
printf " %s DNS 劫持: 激活失败\n" "$ICON_FAIL"
fi
else
printf " %s DNS 劫持: 已禁用\n" "$ICON_OFF"
fi
if nft list chain inet "$NFT_TABLE" "tproxy_lan_v4" >/dev/null 2>&1; then
printf " %s TPROXY 代理: 已激活 (监听端口 %s)\n" "$ICON_OK" "$PROXY_PORT"
else
printf " %s TPROXY 代理: 激活失败\n" "$ICON_FAIL"
fi
printf " %s IP 列表加载状态:\n" "$ICON_INFO"
if nft list set inet "$NFT_TABLE" "$NFT_SET_RESERVED_V4" >/dev/null 2>&1; then
printf " - %s: %s\n" "$NFT_SET_RESERVED_V4" "$ICON_OK 本地 IPv4 已直连"
else
printf " - %s: %s\n" "$NFT_SET_RESERVED_V4" "$ICON_FAIL 未加载"
fi
if nft list set inet "$NFT_TABLE" "$NFT_SET_RESERVED_V6" >/dev/null 2>&1; then
printf " - %s: %s\n" "$NFT_SET_RESERVED_V6" "$ICON_OK 本地 IPv6 已直连"
else
printf " - %s: %s\n" "$NFT_SET_RESERVED_V6" "$ICON_FAIL 未加载"
fi
if nft list set inet "$NFT_TABLE" "$NFT_SET_BYPASS_V4" >/dev/null 2>&1; then
printf " - %s: %s\n" "$NFT_SET_BYPASS_V4" "$ICON_OK 国内 IPv4 已直连"
else
printf " - %s: %s\n" "$NFT_SET_BYPASS_V4" "$ICON_FAIL 未加载"
fi
if nft list set inet "$NFT_TABLE" "$NFT_SET_BYPASS_V6" >/dev/null 2>&1; then
printf " - %s: %s\n" "$NFT_SET_BYPASS_V6" "$ICON_OK 国内 IPv6 已直连"
else
printf " - %s: %s\n" "$NFT_SET_BYPASS_V6" "$ICON_FAIL 未加载"
fi
printf -- "--------------------------------\n"
}
# --- 脚本主入口 ---
check_root
case "$1" in
start)
do_start
;;
stop)
do_stop
;;
restart)
printf "🔄 重启透明代理服务...\n"
do_stop
sleep 1
do_start
;;
status)
do_status
;;
*)
printf "用法: %s {start|stop|restart|status}\n" "$0"
printf " start - 启动透明代理服务\n"
printf " stop - 停止透明代理服务\n"
printf " restart - 重启透明代理服务\n"
printf " status - 查看服务状态\n"
exit 1
;;
esac
exit 0