mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-13 20:08:05 +08:00
🎨 Sync 2026-11-12 18:00:39
This commit is contained in:
1 parent
47f4b4d6ab
commit
ed73e42a08
27 files changed
+1828
-1574
No files matched your search
@@ -8,6 +8,7 @@ config flowproxy 'global'
|
||||
option routing_table '888'
|
||||
option rule_priority '1000'
|
||||
option dns_proxy_enabled '0'
|
||||
option monitor_enabled '0'
|
||||
option proxy_server_dns_port '5353'
|
||||
option log_level 'info'
|
||||
option log_size '1024'
|
||||
|
||||
@@ -5,299 +5,86 @@ START=99
|
||||
STOP=10
|
||||
USE_PROCD=1
|
||||
|
||||
NAME="flowproxy"
|
||||
UCI_CONFIG="flowproxy"
|
||||
LOG_FILE="/var/log/flowproxy.log"
|
||||
NFT_CONF="/tmp/flowproxy/nft.conf"
|
||||
GENERATE_SCRIPT="/usr/share/flowproxy/generate_nft.sh"
|
||||
STATE_FILE="/var/run/flowproxy.running"
|
||||
. /usr/share/flowproxy/common.sh
|
||||
|
||||
get_level_weight() {
|
||||
case "$1" in
|
||||
debug) echo 0 ;;
|
||||
info) echo 1 ;;
|
||||
warn) echo 2 ;;
|
||||
error) echo 3 ;;
|
||||
*) echo 1 ;;
|
||||
esac
|
||||
_stop_unlocked() {
|
||||
log_msg info "Stopping FlowProxy..."
|
||||
# 强制先移除标记,防止状态误报及旧守护进程继续动作
|
||||
rm -f "$STATE_FILE" "$DEGRADED_FILE" "$GEN_FILE"
|
||||
delete_route
|
||||
delete_nft
|
||||
cleanup_dns
|
||||
rm -f "$NFT_CONF"
|
||||
log_msg info "FlowProxy service stopped"
|
||||
}
|
||||
|
||||
log_msg() {
|
||||
local level="$1"; shift
|
||||
local msg="$*"
|
||||
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
||||
|
||||
# 等级过滤
|
||||
local conf_level=$(uci -q get "$UCI_CONFIG.global.log_level" || echo "info")
|
||||
local conf_weight=$(get_level_weight "$conf_level")
|
||||
local msg_weight=$(get_level_weight "$level")
|
||||
[ "$msg_weight" -lt "$conf_weight" ] && return
|
||||
_start_unlocked() {
|
||||
local enabled=$(uci -q get "$UCI_CONFIG.global.enabled")
|
||||
|
||||
# 简单的日志截断逻辑,防止日志无限增长
|
||||
# 读取配置的大小限制,默认 1024KB
|
||||
local max_size=$(uci -q get "$UCI_CONFIG.global.log_size" || echo 1024)
|
||||
local current_size=$(du -k "$LOG_FILE" 2>/dev/null | cut -f1)
|
||||
if [ "${current_size:-0}" -gt "$max_size" ]; then
|
||||
mv "$LOG_FILE" "$LOG_FILE.old"
|
||||
touch "$LOG_FILE"
|
||||
fi
|
||||
if [ "${enabled:-0}" != "1" ]; then
|
||||
log_msg info "FlowProxy is disabled, ensuring service is stopped"
|
||||
_stop_unlocked
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "[$timestamp] [$level] $msg" >> "$LOG_FILE"
|
||||
}
|
||||
log_msg info "Starting FlowProxy..."
|
||||
|
||||
check_env() {
|
||||
# 检测核心二进制工具是否存在
|
||||
if ! command -v nft >/dev/null 2>&1; then
|
||||
log_msg error "nftables binary not found"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
check_env || return 1
|
||||
check_config || return 1
|
||||
apply_nft_rules || return 1
|
||||
add_route || { delete_nft; return 1; }
|
||||
setup_dns
|
||||
|
||||
check_config() {
|
||||
local proxy_server_ip_addr=$(uci -q get "$UCI_CONFIG.global.proxy_server_ip_addr")
|
||||
[ -z "$proxy_server_ip_addr" ] && { log_msg error "proxy_server_ip_addr not configured"; return 1; }
|
||||
return 0
|
||||
}
|
||||
rm -f "$DEGRADED_FILE"
|
||||
touch "$STATE_FILE"
|
||||
|
||||
apply_nft_rules() {
|
||||
log_msg info "Generating nftables configuration..."
|
||||
local conf_level=$(uci -q get "$UCI_CONFIG.global.log_level" || echo "info")
|
||||
local gen="$(date +%s)-$$"
|
||||
echo "$gen" > "$GEN_FILE"
|
||||
|
||||
mkdir -p "$(dirname "$NFT_CONF")"
|
||||
if [ -x "$GENERATE_SCRIPT" ]; then
|
||||
# 传递日志等级并捕获 stderr 到临时文件
|
||||
local err_file="/tmp/flowproxy/gen_err.tmp"
|
||||
FLOWPROXY_LOG_LEVEL="$conf_level" "$GENERATE_SCRIPT" >/dev/null 2> "$err_file"
|
||||
local ret=$?
|
||||
|
||||
# 将脚本的输出记录到日志
|
||||
if [ -s "$err_file" ]; then
|
||||
while read -r line; do
|
||||
log_msg debug "Script: $line"
|
||||
done < "$err_file"
|
||||
rm -f "$err_file"
|
||||
fi
|
||||
local monitor_enabled=$(uci -q get "$UCI_CONFIG.global.monitor_enabled" || echo 0)
|
||||
if [ "$monitor_enabled" = "1" ]; then
|
||||
procd_open_instance "watchdog"
|
||||
procd_set_param command /usr/share/flowproxy/watchdog.sh "$gen"
|
||||
procd_set_param respawn 3600 5 5
|
||||
procd_close_instance
|
||||
fi
|
||||
|
||||
if [ $ret -eq 0 ]; then
|
||||
log_msg info "Applying nftables rules from $NFT_CONF"
|
||||
# 确保内核支持 inet 表
|
||||
nft delete table inet flowproxy 2>/dev/null
|
||||
if nft -f "$NFT_CONF"; then
|
||||
return 0
|
||||
else
|
||||
log_msg error "nftables rules application failed (syntax error)"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
log_msg error "Failed to generate nftables config"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
log_msg error "Generator script $GENERATE_SCRIPT not found or not executable"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
add_route() {
|
||||
local proxy_server_ip_addr=$(uci -q get "$UCI_CONFIG.global.proxy_server_ip_addr")
|
||||
local interface=$(uci -q get "$UCI_CONFIG.global.interface" || echo "br-lan")
|
||||
local traffic_mark=$(uci -q get "$UCI_CONFIG.global.traffic_mark" || echo "0x666")
|
||||
local routing_table=$(uci -q get "$UCI_CONFIG.global.routing_table" || echo "888")
|
||||
local rule_priority=$(uci -q get "$UCI_CONFIG.global.rule_priority" || echo "1000")
|
||||
|
||||
# 1. 先尝试用旧记录清理一次,防止由于配置变更导致的残留
|
||||
delete_route "quiet"
|
||||
|
||||
log_msg info "Setting up routing table $routing_table and rule for mark $traffic_mark with priority $rule_priority..."
|
||||
|
||||
# 2. 记录当前使用的标记值、路由表 ID 和优先级
|
||||
echo "$traffic_mark:$routing_table:$rule_priority" > "/var/run/flowproxy.mark"
|
||||
|
||||
# 3. 再次确保新标记和表的规则是干净的
|
||||
while ip rule del fwmark "$traffic_mark" lookup "$routing_table" 2>/dev/null; do :; done
|
||||
ip route flush table "$routing_table" 2>/dev/null
|
||||
|
||||
# 4. 添加路由和策略
|
||||
local err
|
||||
ip route add default via "$proxy_server_ip_addr" dev "$interface" table "$routing_table" 2>/dev/null
|
||||
err=$(ip rule add fwmark "$traffic_mark" lookup "$routing_table" priority "$rule_priority" 2>&1)
|
||||
if [ $? -eq 0 ]; then
|
||||
log_msg info "Route added: via $proxy_server_ip_addr dev $interface table $routing_table for mark $traffic_mark with priority $rule_priority"
|
||||
else
|
||||
log_msg error "Failed to add ip rule: $err"
|
||||
fi
|
||||
}
|
||||
|
||||
delete_route() {
|
||||
local traffic_mark routing_table rule_priority
|
||||
local quiet="$1"
|
||||
|
||||
# 1. 获取两组清理目标:旧值(记录文件) 和 新值(当前UCI)
|
||||
local old_mark="" old_table="" old_prio=""
|
||||
if [ -f "/var/run/flowproxy.mark" ]; then
|
||||
local saved_val=$(cat "/var/run/flowproxy.mark")
|
||||
old_mark=$(echo "$saved_val" | cut -d':' -f1)
|
||||
old_table=$(echo "$saved_val" | cut -d':' -f2)
|
||||
old_prio=$(echo "$saved_val" | cut -d':' -f3)
|
||||
[ "$quiet" != "quiet" ] && rm -f "/var/run/flowproxy.mark"
|
||||
fi
|
||||
|
||||
local curr_mark=$(uci -q get "$UCI_CONFIG.global.traffic_mark" || echo "0x666")
|
||||
local curr_table=$(uci -q get "$UCI_CONFIG.global.routing_table" || echo "888")
|
||||
local curr_prio=$(uci -q get "$UCI_CONFIG.global.rule_priority" || echo "1000")
|
||||
|
||||
[ "$quiet" != "quiet" ] && log_msg info "Cleaning up FlowProxy routes (Atomic Mode)..."
|
||||
|
||||
# 2. 核心清理函数:只清理带 fwmark 且指向特定 table 的规则
|
||||
safe_del_rule() {
|
||||
local m="$1" t="$2" p="$3"
|
||||
[ -z "$m" ] || [ -z "$t" ] && return
|
||||
log_msg debug "Attempting cleanup: mark=$m, table=$t, priority=${p:-any}"
|
||||
# 精准清理 (带优先级)
|
||||
[ -n "$p" ] && while ip rule del fwmark "$m" lookup "$t" priority "$p" 2>/dev/null; do
|
||||
log_msg debug "Removed stale ip rule: mark $m lookup $t priority $p"
|
||||
done
|
||||
# 模糊清理 (不带优先级,防止配置中途被手动改动)
|
||||
while ip rule del fwmark "$m" lookup "$t" 2>/dev/null; do
|
||||
log_msg debug "Removed stale ip rule (fuzzy): mark $m lookup $t"
|
||||
done
|
||||
# 清空路由表
|
||||
ip route flush table "$t" 2>/dev/null
|
||||
log_msg debug "Flushed routing table $t"
|
||||
}
|
||||
|
||||
# 执行清理:先清旧值,再清当前值 (防止残留)
|
||||
safe_del_rule "$old_mark" "$old_table" "$old_prio"
|
||||
[ "$old_mark" != "$curr_mark" ] || [ "$old_table" != "$curr_table" ] && \
|
||||
safe_del_rule "$curr_mark" "$curr_table" "$curr_prio"
|
||||
}
|
||||
|
||||
delete_nft() {
|
||||
nft delete table inet flowproxy 2>/dev/null
|
||||
log_msg info "Removed nftables table 'flowproxy'"
|
||||
}
|
||||
|
||||
setup_dns() {
|
||||
local enabled=$(uci -q get "$UCI_CONFIG.global.enabled" || echo 0)
|
||||
local dns_proxy_enabled=$(uci -q get "$UCI_CONFIG.global.dns_proxy_enabled" || echo 0)
|
||||
local proxy_server_dns_port=$(uci -q get "$UCI_CONFIG.global.proxy_server_dns_port" || echo 5353)
|
||||
local proxy_server_ip_addr=$(uci -q get "$UCI_CONFIG.global.proxy_server_ip_addr")
|
||||
|
||||
# Only apply DNS proxy if BOTH main service and DNS proxy are enabled
|
||||
if [ "$enabled" -eq 0 ] || [ "$dns_proxy_enabled" -eq 0 ]; then
|
||||
cleanup_dns
|
||||
return 0
|
||||
fi
|
||||
|
||||
[ -z "$proxy_server_ip_addr" ] && { log_msg error "proxy_server_ip_addr not configured, cannot setup DNS"; return 1; }
|
||||
|
||||
log_msg info "Setting up DNS redirection to $proxy_server_ip_addr#$proxy_server_dns_port..."
|
||||
|
||||
# Persistent backup in UCI
|
||||
if ! uci -q get "$UCI_CONFIG.dns_backup" >/dev/null; then
|
||||
local old_noresolv=$(uci -q get dhcp.@dnsmasq[0].noresolv)
|
||||
local old_dns_redirect=$(uci -q get dhcp.@dnsmasq[0].dns_redirect)
|
||||
local old_servers=$(uci -q get dhcp.@dnsmasq[0].server | tr '\n' ',' | sed 's/,$//')
|
||||
|
||||
log_msg debug "Backing up DNS: noresolv=${old_noresolv:-empty}, redirect=${old_dns_redirect:-empty}, servers=${old_servers:-empty}"
|
||||
|
||||
uci set "$UCI_CONFIG.dns_backup=dns_backup"
|
||||
[ -n "$old_noresolv" ] && uci set "$UCI_CONFIG.dns_backup.noresolv=$old_noresolv"
|
||||
[ -n "$old_dns_redirect" ] && uci set "$UCI_CONFIG.dns_backup.dns_redirect=$old_dns_redirect"
|
||||
[ -n "$old_servers" ] && uci set "$UCI_CONFIG.dns_backup.servers=$old_servers"
|
||||
uci commit "$UCI_CONFIG"
|
||||
fi
|
||||
|
||||
log_msg debug "Applying new DNS: noresolv=1, redirect=0, server=$proxy_server_ip_addr#$proxy_server_dns_port"
|
||||
uci set dhcp.@dnsmasq[0].noresolv='1'
|
||||
uci set dhcp.@dnsmasq[0].dns_redirect='0'
|
||||
|
||||
# Delete current servers and add our proxy
|
||||
uci -q delete dhcp.@dnsmasq[0].server
|
||||
uci add_list dhcp.@dnsmasq[0].server="$proxy_server_ip_addr#$proxy_server_dns_port"
|
||||
|
||||
uci commit dhcp
|
||||
/etc/init.d/dnsmasq restart
|
||||
}
|
||||
|
||||
cleanup_dns() {
|
||||
if uci -q get "$UCI_CONFIG.dns_backup" >/dev/null; then
|
||||
log_msg info "Restoring original DNS configuration..."
|
||||
|
||||
local noresolv=$(uci -q get "$UCI_CONFIG.dns_backup.noresolv")
|
||||
local dns_redirect=$(uci -q get "$UCI_CONFIG.dns_backup.dns_redirect")
|
||||
local servers=$(uci -q get "$UCI_CONFIG.dns_backup.servers")
|
||||
|
||||
log_msg debug "Restoring DNS values: noresolv=${noresolv:-0}, redirect=${dns_redirect:-1}, servers=${servers:-empty}"
|
||||
|
||||
# Restore noresolv (default to 0 if not backed up)
|
||||
[ -n "$noresolv" ] && uci set dhcp.@dnsmasq[0].noresolv="$noresolv" || uci set dhcp.@dnsmasq[0].noresolv="0"
|
||||
|
||||
# Restore dns_redirect (only restore if it originally existed, avoid polluting config)
|
||||
if [ -n "$dns_redirect" ]; then
|
||||
uci set dhcp.@dnsmasq[0].dns_redirect="$dns_redirect"
|
||||
else
|
||||
uci -q delete dhcp.@dnsmasq[0].dns_redirect
|
||||
fi
|
||||
|
||||
# Restore servers correctly splitting comma-separated list
|
||||
uci -q delete dhcp.@dnsmasq[0].server
|
||||
if [ -n "$servers" ]; then
|
||||
for s in $(echo "$servers" | tr ',' ' '); do
|
||||
[ -n "$s" ] && uci add_list dhcp.@dnsmasq[0].server="$s"
|
||||
done
|
||||
fi
|
||||
|
||||
uci commit dhcp
|
||||
uci delete "$UCI_CONFIG.dns_backup"
|
||||
uci commit "$UCI_CONFIG"
|
||||
/etc/init.d/dnsmasq restart
|
||||
fi
|
||||
log_msg info "FlowProxy service started successfully"
|
||||
return 0
|
||||
}
|
||||
|
||||
start_service() {
|
||||
local enabled=$(uci -q get "$UCI_CONFIG.global.enabled" || echo 0)
|
||||
|
||||
if [ "$enabled" -eq 0 ]; then
|
||||
log_msg info "FlowProxy is disabled, ensuring service is stopped"
|
||||
stop_service
|
||||
return 0
|
||||
fi
|
||||
|
||||
log_msg info "Starting FlowProxy..."
|
||||
|
||||
check_env || return 1
|
||||
check_config || return 1
|
||||
apply_nft_rules || return 1
|
||||
add_route
|
||||
setup_dns
|
||||
|
||||
touch "$STATE_FILE"
|
||||
log_msg info "FlowProxy service started successfully"
|
||||
acquire_lock
|
||||
_start_unlocked
|
||||
local ret=$?
|
||||
release_lock
|
||||
return $ret
|
||||
}
|
||||
|
||||
stop_service() {
|
||||
log_msg info "Stopping FlowProxy..."
|
||||
# 强制先移除标记,防止状态误报
|
||||
rm -f "$STATE_FILE"
|
||||
delete_route
|
||||
delete_nft
|
||||
cleanup_dns
|
||||
log_msg info "FlowProxy service stopped"
|
||||
acquire_lock
|
||||
_stop_unlocked
|
||||
local ret=$?
|
||||
release_lock
|
||||
return $ret
|
||||
}
|
||||
|
||||
restart_service() {
|
||||
stop_service
|
||||
start_service
|
||||
acquire_lock
|
||||
_stop_unlocked
|
||||
_start_unlocked
|
||||
local ret=$?
|
||||
release_lock
|
||||
return $ret
|
||||
}
|
||||
|
||||
reload_service() {
|
||||
restart_service
|
||||
restart_service
|
||||
}
|
||||
|
||||
service_triggers() {
|
||||
# 监听自身配置改动
|
||||
procd_add_reload_trigger "$UCI_CONFIG"
|
||||
# 监听防火墙重载事件,防止规则被 fw4 清空后无法自动恢复
|
||||
procd_add_reload_trigger "firewall"
|
||||
# 监听自身配置改动
|
||||
procd_add_reload_trigger "$UCI_CONFIG"
|
||||
# 监听防火墙重载事件,防止规则被 fw4 清空后无法自动恢复
|
||||
procd_add_reload_trigger "firewall"
|
||||
}
|
||||
+328
@@ -0,0 +1,328 @@
|
||||
#!/bin/sh
|
||||
# FlowProxy shared runtime helpers
|
||||
|
||||
UCI_CONFIG="flowproxy"
|
||||
LOG_FILE="/var/log/flowproxy.log"
|
||||
NFT_CONF="/tmp/flowproxy/nft.conf"
|
||||
GENERATE_SCRIPT="/usr/share/flowproxy/generate_nft.sh"
|
||||
STATE_FILE="/var/run/flowproxy.running"
|
||||
DEGRADED_FILE="/var/run/flowproxy.degraded"
|
||||
GEN_FILE="/var/run/flowproxy.gen"
|
||||
MARK_FILE="/var/run/flowproxy.mark"
|
||||
LOCK_FILE="/var/lock/flowproxy.lock"
|
||||
|
||||
get_level_weight() {
|
||||
case "$1" in
|
||||
debug) echo 0 ;;
|
||||
info) echo 1 ;;
|
||||
warn) echo 2 ;;
|
||||
error) echo 3 ;;
|
||||
*) echo 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
log_msg() {
|
||||
local level="$1"; shift
|
||||
local msg="$*"
|
||||
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
||||
|
||||
# 等级过滤
|
||||
local conf_level=$(uci -q get "$UCI_CONFIG.global.log_level")
|
||||
conf_level="${conf_level:-info}"
|
||||
local conf_weight=$(get_level_weight "$conf_level")
|
||||
local msg_weight=$(get_level_weight "$level")
|
||||
[ "$msg_weight" -lt "$conf_weight" ] && return
|
||||
|
||||
# 简单的日志截断逻辑,防止日志无限增长
|
||||
# 读取配置的大小限制,默认 1024KB
|
||||
local max_size=$(uci -q get "$UCI_CONFIG.global.log_size")
|
||||
case "$max_size" in
|
||||
''|*[!0-9]*) max_size=1024 ;;
|
||||
esac
|
||||
local current_size=$(du -k "$LOG_FILE" 2>/dev/null | cut -f1)
|
||||
if [ "${current_size:-0}" -gt "$max_size" ]; then
|
||||
mv "$LOG_FILE" "$LOG_FILE.old"
|
||||
touch "$LOG_FILE"
|
||||
fi
|
||||
|
||||
echo "[$timestamp] [$level] $msg" >> "$LOG_FILE"
|
||||
}
|
||||
|
||||
acquire_lock() {
|
||||
[ "${FLOWPROXY_LOCKED:-0}" = "1" ] && return 0
|
||||
mkdir -p "$(dirname "$LOCK_FILE")"
|
||||
if command -v flock >/dev/null 2>&1; then
|
||||
exec 9>"$LOCK_FILE"
|
||||
flock -x 9
|
||||
else
|
||||
local wait_cnt=0
|
||||
while ! mkdir "${LOCK_FILE}.d" 2>/dev/null; do
|
||||
wait_cnt=$((wait_cnt + 1))
|
||||
[ "$wait_cnt" -ge 50 ] && break
|
||||
sleep 1
|
||||
done
|
||||
fi
|
||||
FLOWPROXY_LOCKED=1
|
||||
return 0
|
||||
}
|
||||
|
||||
release_lock() {
|
||||
[ "${FLOWPROXY_LOCKED:-0}" != "1" ] && return 0
|
||||
if command -v flock >/dev/null 2>&1; then
|
||||
flock -u 9 2>/dev/null
|
||||
exec 9>&-
|
||||
else
|
||||
rmdir "${LOCK_FILE}.d" 2>/dev/null
|
||||
fi
|
||||
FLOWPROXY_LOCKED=0
|
||||
return 0
|
||||
}
|
||||
|
||||
check_env() {
|
||||
# 检测核心二进制工具是否存在
|
||||
if ! command -v nft >/dev/null 2>&1; then
|
||||
log_msg error "nftables binary not found"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
check_config() {
|
||||
local proxy_server_ip_addr=$(uci -q get "$UCI_CONFIG.global.proxy_server_ip_addr")
|
||||
[ -z "$proxy_server_ip_addr" ] && { log_msg error "proxy_server_ip_addr not configured"; return 1; }
|
||||
return 0
|
||||
}
|
||||
|
||||
apply_nft_rules() {
|
||||
log_msg info "Generating nftables configuration..."
|
||||
local conf_level=$(uci -q get "$UCI_CONFIG.global.log_level" || echo "info")
|
||||
|
||||
mkdir -p "$(dirname "$NFT_CONF")"
|
||||
if [ -x "$GENERATE_SCRIPT" ]; then
|
||||
# 传递日志等级并捕获 stderr 到临时文件
|
||||
local err_file="/tmp/flowproxy/gen_err.tmp"
|
||||
FLOWPROXY_LOG_LEVEL="$conf_level" "$GENERATE_SCRIPT" >/dev/null 2> "$err_file"
|
||||
local ret=$?
|
||||
|
||||
# 将脚本的输出记录到日志
|
||||
if [ -s "$err_file" ]; then
|
||||
while read -r line; do
|
||||
log_msg debug "Script: $line"
|
||||
done < "$err_file"
|
||||
fi
|
||||
rm -f "$err_file"
|
||||
|
||||
if [ $ret -eq 0 ]; then
|
||||
log_msg info "Applying nftables rules from $NFT_CONF"
|
||||
# 确保内核支持 inet 表
|
||||
nft delete table inet flowproxy 2>/dev/null
|
||||
if nft -f "$NFT_CONF"; then
|
||||
return 0
|
||||
else
|
||||
log_msg error "nftables rules application failed (syntax error)"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
log_msg error "Failed to generate nftables config"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
log_msg error "Generator script $GENERATE_SCRIPT not found or not executable"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
restore_nft_rules() {
|
||||
if [ -s "$NFT_CONF" ]; then
|
||||
log_msg info "Restoring nftables rules from cached $NFT_CONF"
|
||||
nft delete table inet flowproxy 2>/dev/null
|
||||
if nft -f "$NFT_CONF"; then
|
||||
return 0
|
||||
fi
|
||||
log_msg warn "Cached nftables config failed, regenerating..."
|
||||
fi
|
||||
apply_nft_rules
|
||||
}
|
||||
|
||||
add_route() {
|
||||
local proxy_server_ip_addr=$(uci -q get "$UCI_CONFIG.global.proxy_server_ip_addr")
|
||||
local interface=$(uci -q get "$UCI_CONFIG.global.interface"); interface="${interface:-br-lan}"
|
||||
local traffic_mark=$(uci -q get "$UCI_CONFIG.global.traffic_mark"); traffic_mark="${traffic_mark:-0x666}"
|
||||
local routing_table=$(uci -q get "$UCI_CONFIG.global.routing_table"); routing_table="${routing_table:-888}"
|
||||
local rule_priority=$(uci -q get "$UCI_CONFIG.global.rule_priority"); rule_priority="${rule_priority:-1000}"
|
||||
|
||||
# 1. 先尝试用旧记录清理一次,防止由于配置变更导致的残留
|
||||
delete_route "quiet"
|
||||
|
||||
log_msg info "Setting up routing table $routing_table and rule for mark $traffic_mark with priority $rule_priority..."
|
||||
|
||||
# 2. 记录当前使用的标记值、路由表 ID 和优先级
|
||||
echo "$traffic_mark:$routing_table:$rule_priority" > "$MARK_FILE"
|
||||
|
||||
# 3. 再次确保新标记和表的规则是干净的
|
||||
while ip rule del fwmark "$traffic_mark" lookup "$routing_table" 2>/dev/null; do :; done
|
||||
ip route flush table "$routing_table" 2>/dev/null
|
||||
|
||||
# 4. 添加路由和策略
|
||||
local err
|
||||
if ! err=$(ip route add default via "$proxy_server_ip_addr" dev "$interface" table "$routing_table" 2>&1); then
|
||||
log_msg error "Failed to add default route: $err"
|
||||
return 1
|
||||
fi
|
||||
err=$(ip rule add fwmark "$traffic_mark" lookup "$routing_table" priority "$rule_priority" 2>&1)
|
||||
if [ $? -eq 0 ]; then
|
||||
log_msg info "Route added: via $proxy_server_ip_addr dev $interface table $routing_table for mark $traffic_mark with priority $rule_priority"
|
||||
return 0
|
||||
else
|
||||
log_msg error "Failed to add ip rule: $err"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
delete_route() {
|
||||
local quiet="$1"
|
||||
|
||||
# 1. 获取两组清理目标:旧值(记录文件) 和 新值(当前UCI)
|
||||
local old_mark="" old_table="" old_prio=""
|
||||
if [ -f "$MARK_FILE" ]; then
|
||||
local saved_val=$(cat "$MARK_FILE")
|
||||
old_mark=$(echo "$saved_val" | cut -d':' -f1)
|
||||
old_table=$(echo "$saved_val" | cut -d':' -f2)
|
||||
old_prio=$(echo "$saved_val" | cut -d':' -f3)
|
||||
[ "$quiet" != "quiet" ] && rm -f "$MARK_FILE"
|
||||
fi
|
||||
|
||||
local curr_mark=$(uci -q get "$UCI_CONFIG.global.traffic_mark"); curr_mark="${curr_mark:-0x666}"
|
||||
local curr_table=$(uci -q get "$UCI_CONFIG.global.routing_table"); curr_table="${curr_table:-888}"
|
||||
local curr_prio=$(uci -q get "$UCI_CONFIG.global.rule_priority"); curr_prio="${curr_prio:-1000}"
|
||||
|
||||
[ "$quiet" != "quiet" ] && log_msg info "Cleaning up FlowProxy routes (Atomic Mode)..."
|
||||
|
||||
# 2. 核心清理函数:只清理带 fwmark 且指向特定 table 的规则
|
||||
safe_del_rule() {
|
||||
local m="$1" t="$2" p="$3"
|
||||
[ -z "$m" ] || [ -z "$t" ] && return
|
||||
log_msg debug "Attempting cleanup: mark=$m, table=$t, priority=${p:-any}"
|
||||
# 精准清理 (带优先级)
|
||||
[ -n "$p" ] && while ip rule del fwmark "$m" lookup "$t" priority "$p" 2>/dev/null; do
|
||||
log_msg debug "Removed stale ip rule: mark $m lookup $t priority $p"
|
||||
done
|
||||
# 模糊清理 (不带优先级,防止配置中途被手动改动)
|
||||
while ip rule del fwmark "$m" lookup "$t" 2>/dev/null; do
|
||||
log_msg debug "Removed stale ip rule (fuzzy): mark $m lookup $t"
|
||||
done
|
||||
# 清空路由表
|
||||
ip route flush table "$t" 2>/dev/null
|
||||
log_msg debug "Flushed routing table $t"
|
||||
}
|
||||
|
||||
# 执行清理:先清旧值,再清当前值 (防止残留)
|
||||
safe_del_rule "$old_mark" "$old_table" "$old_prio"
|
||||
[ "$old_mark" != "$curr_mark" ] || [ "$old_table" != "$curr_table" ] && \
|
||||
safe_del_rule "$curr_mark" "$curr_table" "$curr_prio"
|
||||
}
|
||||
|
||||
delete_nft() {
|
||||
nft delete table inet flowproxy 2>/dev/null
|
||||
log_msg info "Removed nftables table 'flowproxy'"
|
||||
}
|
||||
|
||||
setup_dns() {
|
||||
local enabled=$(uci -q get "$UCI_CONFIG.global.enabled")
|
||||
local dns_proxy_enabled=$(uci -q get "$UCI_CONFIG.global.dns_proxy_enabled")
|
||||
local proxy_server_dns_port=$(uci -q get "$UCI_CONFIG.global.proxy_server_dns_port"); proxy_server_dns_port="${proxy_server_dns_port:-5353}"
|
||||
local proxy_server_ip_addr=$(uci -q get "$UCI_CONFIG.global.proxy_server_ip_addr")
|
||||
|
||||
# Only apply DNS proxy if BOTH main service and DNS proxy are enabled
|
||||
if [ "${enabled:-0}" != "1" ] || [ "${dns_proxy_enabled:-0}" != "1" ]; then
|
||||
cleanup_dns
|
||||
return 0
|
||||
fi
|
||||
|
||||
[ -z "$proxy_server_ip_addr" ] && { log_msg error "proxy_server_ip_addr not configured, cannot setup DNS"; return 1; }
|
||||
|
||||
log_msg info "Setting up DNS redirection to $proxy_server_ip_addr#$proxy_server_dns_port..."
|
||||
|
||||
# Persistent backup in UCI
|
||||
if ! uci -q get "$UCI_CONFIG.dns_backup" >/dev/null; then
|
||||
local old_noresolv=$(uci -q get dhcp.@dnsmasq[0].noresolv)
|
||||
local old_dns_redirect=$(uci -q get dhcp.@dnsmasq[0].dns_redirect)
|
||||
local old_servers=$(uci -q get dhcp.@dnsmasq[0].server | tr '\n' ',' | sed 's/,$//')
|
||||
|
||||
log_msg debug "Backing up DNS: noresolv=${old_noresolv:-empty}, redirect=${old_dns_redirect:-empty}, servers=${old_servers:-empty}"
|
||||
|
||||
uci set "$UCI_CONFIG.dns_backup=dns_backup"
|
||||
[ -n "$old_noresolv" ] && uci set "$UCI_CONFIG.dns_backup.noresolv=$old_noresolv"
|
||||
[ -n "$old_dns_redirect" ] && uci set "$UCI_CONFIG.dns_backup.dns_redirect=$old_dns_redirect"
|
||||
[ -n "$old_servers" ] && uci set "$UCI_CONFIG.dns_backup.servers=$old_servers"
|
||||
uci commit "$UCI_CONFIG"
|
||||
fi
|
||||
|
||||
log_msg debug "Applying new DNS: noresolv=1, redirect=0, server=$proxy_server_ip_addr#$proxy_server_dns_port"
|
||||
uci set dhcp.@dnsmasq[0].noresolv='1'
|
||||
uci set dhcp.@dnsmasq[0].dns_redirect='0'
|
||||
|
||||
# Delete current servers and add our proxy
|
||||
uci -q delete dhcp.@dnsmasq[0].server
|
||||
uci add_list dhcp.@dnsmasq[0].server="$proxy_server_ip_addr#$proxy_server_dns_port"
|
||||
|
||||
uci commit dhcp
|
||||
/etc/init.d/dnsmasq restart
|
||||
}
|
||||
|
||||
cleanup_dns() {
|
||||
if uci -q get "$UCI_CONFIG.dns_backup" >/dev/null; then
|
||||
log_msg info "Restoring original DNS configuration..."
|
||||
|
||||
local noresolv=$(uci -q get "$UCI_CONFIG.dns_backup.noresolv")
|
||||
local dns_redirect=$(uci -q get "$UCI_CONFIG.dns_backup.dns_redirect")
|
||||
local servers=$(uci -q get "$UCI_CONFIG.dns_backup.servers")
|
||||
|
||||
log_msg debug "Restoring DNS values: noresolv=${noresolv:-0}, redirect=${dns_redirect:-1}, servers=${servers:-empty}"
|
||||
|
||||
# Restore noresolv (default to 0 if not backed up)
|
||||
[ -n "$noresolv" ] && uci set dhcp.@dnsmasq[0].noresolv="$noresolv" || uci set dhcp.@dnsmasq[0].noresolv="0"
|
||||
|
||||
# Restore dns_redirect (only restore if it originally existed, avoid polluting config)
|
||||
if [ -n "$dns_redirect" ]; then
|
||||
uci set dhcp.@dnsmasq[0].dns_redirect="$dns_redirect"
|
||||
else
|
||||
uci -q delete dhcp.@dnsmasq[0].dns_redirect
|
||||
fi
|
||||
|
||||
# Restore servers correctly splitting comma-separated list
|
||||
uci -q delete dhcp.@dnsmasq[0].server
|
||||
if [ -n "$servers" ]; then
|
||||
for s in $(echo "$servers" | tr ',' ' '); do
|
||||
[ -n "$s" ] && uci add_list dhcp.@dnsmasq[0].server="$s"
|
||||
done
|
||||
fi
|
||||
|
||||
uci commit dhcp
|
||||
uci delete "$UCI_CONFIG.dns_backup"
|
||||
uci commit "$UCI_CONFIG"
|
||||
/etc/init.d/dnsmasq restart
|
||||
fi
|
||||
}
|
||||
|
||||
do_degrade() {
|
||||
local proxy_ip=$(uci -q get "$UCI_CONFIG.global.proxy_server_ip_addr")
|
||||
log_msg warn "Proxy server ${proxy_ip:-unknown} unreachable, degrading FlowProxy to direct mode..."
|
||||
# 先写入降级标记,避免 RPCD 状态自愈误删 STATE_FILE
|
||||
touch "$DEGRADED_FILE"
|
||||
delete_route
|
||||
delete_nft
|
||||
cleanup_dns
|
||||
log_msg warn "FlowProxy degraded to direct mode (monitoring remains active)"
|
||||
}
|
||||
|
||||
do_recover() {
|
||||
local proxy_ip=$(uci -q get "$UCI_CONFIG.global.proxy_server_ip_addr")
|
||||
log_msg info "Proxy server ${proxy_ip:-unknown} is online, recovering FlowProxy..."
|
||||
restore_nft_rules || return 1
|
||||
add_route || { delete_nft; return 1; }
|
||||
setup_dns
|
||||
rm -f "$DEGRADED_FILE"
|
||||
log_msg info "FlowProxy recovered to active proxy mode"
|
||||
return 0
|
||||
}
|
||||
+171
@@ -0,0 +1,171 @@
|
||||
#!/bin/sh
|
||||
# FlowProxy sidecar watchdog daemon (ARP + Ping health probe)
|
||||
|
||||
. /usr/share/flowproxy/common.sh
|
||||
|
||||
MY_GEN="$1"
|
||||
[ -z "$MY_GEN" ] && exit 0
|
||||
|
||||
STOP_REQUESTED=0
|
||||
SLEEP_PID=""
|
||||
|
||||
handle_signal() {
|
||||
STOP_REQUESTED=1
|
||||
[ -n "$SLEEP_PID" ] && kill "$SLEEP_PID" 2>/dev/null
|
||||
}
|
||||
|
||||
trap 'handle_signal' TERM INT
|
||||
|
||||
interruptible_sleep() {
|
||||
local secs="$1"
|
||||
[ "$STOP_REQUESTED" -eq 1 ] && return 1
|
||||
sleep "$secs" &
|
||||
SLEEP_PID=$!
|
||||
wait "$SLEEP_PID" 2>/dev/null
|
||||
SLEEP_PID=""
|
||||
[ "$STOP_REQUESTED" -eq 1 ] && return 1
|
||||
return 0
|
||||
}
|
||||
|
||||
resolve_probe_iface() {
|
||||
local iface="$1"
|
||||
if [ -L "/sys/class/net/$iface/master" ]; then
|
||||
local master_path=$(readlink "/sys/class/net/$iface/master" 2>/dev/null)
|
||||
local master_name=$(basename "$master_path" 2>/dev/null)
|
||||
[ -n "$master_name" ] && { echo "$master_name"; return; }
|
||||
fi
|
||||
echo "$iface"
|
||||
}
|
||||
|
||||
get_uint_or_default() {
|
||||
local val="$1"
|
||||
local def="$2"
|
||||
local min="$3"
|
||||
case "$val" in
|
||||
''|*[!0-9]*) echo "$def"; return ;;
|
||||
esac
|
||||
[ "$val" -lt "$min" ] && echo "$min" || echo "$val"
|
||||
}
|
||||
|
||||
is_context_valid() {
|
||||
[ "$STOP_REQUESTED" -eq 0 ] || return 1
|
||||
[ -f "$STATE_FILE" ] || return 1
|
||||
[ "$(cat "$GEN_FILE" 2>/dev/null)" = "$MY_GEN" ] || return 1
|
||||
[ "$(uci -q get "$UCI_CONFIG.global.enabled" || echo 0)" = "1" ] || return 1
|
||||
[ "$(uci -q get "$UCI_CONFIG.global.monitor_enabled" || echo 0)" = "1" ] || return 1
|
||||
return 0
|
||||
}
|
||||
|
||||
check_proxy_alive() {
|
||||
local ip="$1"
|
||||
local iface="$2"
|
||||
|
||||
# 1. ICMP Ping (首次成功即毫秒级返回;失败则补测一次防单包抖动)
|
||||
if ping -c 1 -W 1 -I "$iface" "$ip" >/dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
if [ "$STOP_REQUESTED" -eq 1 ]; then
|
||||
return 0
|
||||
fi
|
||||
if ping -c 1 -W 1 -I "$iface" "$ip" >/dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
# 2. ARP 互补探测 (防止旁路由禁 Ping 误报)
|
||||
if command -v arping >/dev/null 2>&1; then
|
||||
if arping -f -q -c 2 -w 1 -I "$iface" "$ip" >/dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
else
|
||||
ip neigh flush to "$ip" dev "$iface" >/dev/null 2>&1
|
||||
ping -c 1 -W 1 -I "$iface" "$ip" >/dev/null 2>&1
|
||||
if ip neigh show to "$ip" dev "$iface" 2>/dev/null | grep -qE "REACHABLE|DELAY|PROBE"; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
proxy_ip=$(uci -q get "$UCI_CONFIG.global.proxy_server_ip_addr")
|
||||
conf_iface=$(uci -q get "$UCI_CONFIG.global.interface")
|
||||
conf_iface="${conf_iface:-br-lan}"
|
||||
probe_iface=$(resolve_probe_iface "$conf_iface")
|
||||
|
||||
interval=$(get_uint_or_default "$(uci -q get "$UCI_CONFIG.global.monitor_interval")" 5 2)
|
||||
fail_threshold=$(get_uint_or_default "$(uci -q get "$UCI_CONFIG.global.monitor_fail_threshold")" 3 1)
|
||||
recover_threshold=$(get_uint_or_default "$(uci -q get "$UCI_CONFIG.global.monitor_recover_threshold")" 2 1)
|
||||
|
||||
[ -z "$proxy_ip" ] && exit 0
|
||||
|
||||
log_msg info "Watchdog started (target=$proxy_ip, iface=$probe_iface, interval=${interval}s, fail=$fail_threshold, recover=$recover_threshold)"
|
||||
|
||||
# 启动后先等待一个探测周期,确保主服务完全进入稳定运行态
|
||||
interruptible_sleep "$interval" || exit 0
|
||||
|
||||
[ -f "$DEGRADED_FILE" ] && state="degraded" || state="online"
|
||||
fail_count=0
|
||||
ok_count=0
|
||||
|
||||
while is_context_valid; do
|
||||
if check_proxy_alive "$proxy_ip" "$probe_iface"; then
|
||||
fail_count=0
|
||||
if [ "$state" = "degraded" ]; then
|
||||
ok_count=$((ok_count + 1))
|
||||
log_msg debug "Watchdog: probe succeeded ($ok_count/$recover_threshold)"
|
||||
if [ "$ok_count" -ge "$recover_threshold" ]; then
|
||||
acquire_lock
|
||||
if ! is_context_valid; then
|
||||
release_lock
|
||||
break
|
||||
fi
|
||||
if [ -f "$DEGRADED_FILE" ]; then
|
||||
if do_recover; then
|
||||
state="online"
|
||||
ok_count=0
|
||||
fail_count=0
|
||||
else
|
||||
log_msg error "Watchdog: recovery failed, staying in degraded mode"
|
||||
fi
|
||||
else
|
||||
state="online"
|
||||
ok_count=0
|
||||
fi
|
||||
release_lock
|
||||
fi
|
||||
else
|
||||
ok_count=0
|
||||
fi
|
||||
else
|
||||
[ "$STOP_REQUESTED" -eq 1 ] && break
|
||||
ok_count=0
|
||||
if [ "$state" = "online" ]; then
|
||||
fail_count=$((fail_count + 1))
|
||||
log_msg warn "Watchdog: probe failed ($fail_count/$fail_threshold) for $proxy_ip via $probe_iface"
|
||||
if [ "$fail_count" -ge "$fail_threshold" ]; then
|
||||
acquire_lock
|
||||
if ! is_context_valid; then
|
||||
release_lock
|
||||
break
|
||||
fi
|
||||
if [ ! -f "$DEGRADED_FILE" ]; then
|
||||
do_degrade
|
||||
state="degraded"
|
||||
fail_count=0
|
||||
ok_count=0
|
||||
else
|
||||
state="degraded"
|
||||
fail_count=0
|
||||
fi
|
||||
release_lock
|
||||
fi
|
||||
else
|
||||
fail_count=0
|
||||
fi
|
||||
fi
|
||||
|
||||
interruptible_sleep "$interval" || break
|
||||
done
|
||||
|
||||
log_msg info "Watchdog stopped"
|
||||
exit 0
|
||||
@@ -6,6 +6,7 @@ import { connect } from 'ubus';
|
||||
|
||||
const UCI_CONFIG = 'flowproxy';
|
||||
const STATE_FILE = '/var/run/flowproxy.running';
|
||||
const DEGRADED_FILE = '/var/run/flowproxy.degraded';
|
||||
const IP_CACHE = '/tmp/flowproxy/lan_ip.cache';
|
||||
const LOG_FILE = '/var/log/flowproxy.log';
|
||||
const NFT_TABLE = 'flowproxy';
|
||||
@@ -20,6 +21,7 @@ const methods = {
|
||||
call: function() {
|
||||
// 1. 快速探测
|
||||
let is_running = access(STATE_FILE, 'f');
|
||||
let is_degraded = access(DEGRADED_FILE, 'f');
|
||||
|
||||
// 2. 自愈:检查内核状态并同步标记文件
|
||||
let tcp_active = (system('nft list chain inet ' + NFT_TABLE + ' LAN_MARKFLOW_TCP >/dev/null 2>&1') === 0);
|
||||
@@ -28,7 +30,7 @@ const methods = {
|
||||
|
||||
if (real_running && !is_running) {
|
||||
try { writefile(STATE_FILE, ''); } catch(e) {}
|
||||
} else if (!real_running && is_running) {
|
||||
} else if (!real_running && is_running && !is_degraded) {
|
||||
try { system('rm -f ' + STATE_FILE); } catch(e) {}
|
||||
}
|
||||
|
||||
@@ -67,11 +69,15 @@ const methods = {
|
||||
|
||||
let proxy_server_ip_addr = uci_cursor.get(UCI_CONFIG, 'global', 'proxy_server_ip_addr') || '';
|
||||
let dns_running = (uci_cursor.get(UCI_CONFIG, 'dns_backup') ? 1 : 0);
|
||||
let monitor_enabled = int(uci_cursor.get(UCI_CONFIG, 'global', 'monitor_enabled') || '0');
|
||||
let degraded_active = (is_degraded && is_running && !real_running) ? 1 : 0;
|
||||
|
||||
return {
|
||||
running: real_running ? 1 : 0,
|
||||
running: (real_running || degraded_active) ? 1 : 0,
|
||||
degraded: degraded_active,
|
||||
dns_running: dns_running,
|
||||
enabled: int(uci_cursor.get(UCI_CONFIG, 'global', 'enabled') || '0'),
|
||||
monitor_enabled: monitor_enabled,
|
||||
proxy_server_ip_addr: proxy_server_ip_addr,
|
||||
proxy_server_dns_port: uci_cursor.get(UCI_CONFIG, 'global', 'proxy_server_dns_port') || '5353',
|
||||
lan_ip: safe_trim(lan_ip),
|
||||
|
||||
Reference in new issue
Block a user