🍓 Sync 2026-09-15 02:13:06

This commit is contained in:
github-actions[bot] committed 2026-09-15 02:13:06 +08:00
1 parent 0e5393594c
commit 1dd96ac2bb
146 files changed
+26541 -508

No files matched your search

@@ -0,0 +1,104 @@
config flowproxy 'global'
option enabled '0'
option tcp_enabled '1'
option udp_enabled '1'
option proxy_server_ip_addr ''
option interface 'br-lan'
option traffic_mark '0x666'
option routing_table '888'
option rule_priority '1000'
option dns_proxy_enabled '0'
option proxy_server_dns_port '5353'
option log_level 'info'
option log_size '1024'
option log_count '3'
# --- TCP 默认规则 ---
config tcp_rule
option enabled '1'
option match_type 'src_mac'
option match_value '@no_proxy_src_mac'
option action 'return'
option counter '0'
config tcp_rule
option enabled '1'
option match_type 'dst_ip'
option match_value '@private_dst_ip_v4'
option action 'return'
option counter '0'
config tcp_rule
option enabled '1'
option match_type 'dst_ip'
option match_value '@chnroute_dst_ip_v4'
option action 'return'
option counter '0'
config tcp_rule
option enabled '1'
option match_type 'dst_port'
option match_value '@no_proxy_dst_tcp_ports'
option action 'return'
option counter '0'
# --- UDP 默认规则 ---
config udp_rule
option enabled '1'
option match_type 'src_mac'
option match_value '@no_proxy_src_mac'
option action 'return'
option counter '0'
config udp_rule
option enabled '1'
option match_type 'dst_ip'
option match_value '@private_dst_ip_v4'
option action 'return'
option counter '0'
config udp_rule
option enabled '1'
option match_type 'dst_ip'
option match_value '@chnroute_dst_ip_v4'
option action 'return'
option counter '0'
config udp_rule
option enabled '1'
option match_type 'dst_port'
option match_value '@no_proxy_dst_udp_ports'
option action 'return'
option counter '0'
# nftables set 配置
config nftset 'no_proxy_src_mac'
option enabled '1'
option type 'ether_addr'
config nftset 'no_proxy_src_ip_v4'
option enabled '1'
option type 'ipv4_addr'
config nftset 'no_proxy_dst_ip_v4'
option enabled '1'
option type 'ipv4_addr'
config nftset 'private_dst_ip_v4'
option enabled '1'
option type 'ipv4_addr'
option auto_generate '1'
config nftset 'chnroute_dst_ip_v4'
option enabled '1'
option type 'ipv4_addr'
option file_path '/usr/share/flowproxy/chnroute.txt'
option download_url 'https://proxy.seckv.com/https://raw.githubusercontent.com/mayaxcn/china-ip-list/master/chnroute.txt'
config nftset 'no_proxy_dst_tcp_ports'
option enabled '1'
option type 'inet_service'
config nftset 'no_proxy_dst_udp_ports'
option enabled '1'
option type 'inet_service'
+299
View File
@@ -0,0 +1,299 @@
#!/bin/sh /etc/rc.common
# FlowProxy - Traffic Diversion Service
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"
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" || echo "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" || 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
echo "[$timestamp] [$level] $msg" >> "$LOG_FILE"
}
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"
rm -f "$err_file"
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 (default to 1 as requested for OpenWrt typical modded builds)
[ -n "$dns_redirect" ] && uci set dhcp.@dnsmasq[0].dns_redirect="$dns_redirect" || uci set dhcp.@dnsmasq[0].dns_redirect="1"
# Restore servers
uci -q delete dhcp.@dnsmasq[0].server
if [ -n "$servers" ]; then
for s in $servers; 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
}
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"
}
stop_service() {
log_msg info "Stopping FlowProxy..."
# 强制先移除标记,防止状态误报
rm -f "$STATE_FILE"
delete_route
delete_nft
cleanup_dns
log_msg info "FlowProxy service stopped"
}
restart_service() {
stop_service
start_service
}
service_triggers() {
# 监听自身配置改动
procd_add_reload_trigger "$UCI_CONFIG"
# 监听防火墙重载事件,防止规则被 fw4 清空后无法自动恢复
procd_add_reload_trigger "firewall"
}
reload_service() {
restart
}
File diff suppressed because it is too large. Load diff
+215
View File
@@ -0,0 +1,215 @@
#!/bin/sh
# Generate nftables configuration for flowproxy
. /lib/functions.sh
[ -n "$UCI_CONFIG_DIR" ] && export UCI_CONFIG_DIR
CONFIG="flowproxy"
NFT_TABLE="flowproxy"
OUTPUT_FILE="/tmp/flowproxy/nft.conf"
mkdir -p "$(dirname "$OUTPUT_FILE")"
ENABLED_SETS=" "
log_debug() {
[ "$FLOWPROXY_LOG_LEVEL" = "debug" ] && echo "DEBUG: $*" >&2
}
gen_set_definition() {
local section="$1"
local type enabled auto_gen file_path elems is_interval
enabled=$(uci -q get "$CONFIG.$section.enabled")
[ "$enabled" = "0" ] && { log_debug "Set $section is disabled, skipping"; return; }
log_debug "Generating set: $section"
ENABLED_SETS="${ENABLED_SETS}@${section} "
type=$(uci -q get "$CONFIG.$section.type")
[ -z "$type" ] && type="ipv4_addr"
if [ "$section" = "private_dst_ip_v4" ]; then
auto_gen=$(uci -q get "$CONFIG.$section.auto_generate")
if [ "$auto_gen" = "1" ] || [ -z "$auto_gen" ]; then
elems="10.0.0.0/8, 127.0.0.0/8, 169.254.0.0/16, 172.16.0.0/12, 192.168.0.0/16"
fi
fi
local uci_elems=""
for e in $(uci -q get "$CONFIG.$section.elements"); do
[ -n "$uci_elems" ] && uci_elems="${uci_elems}, "
uci_elems="${uci_elems}${e}"
done
[ -n "$uci_elems" ] && { [ -n "$elems" ] && elems="${elems}, "; elems="${elems}${uci_elems}"; }
file_path=$(uci -q get "$CONFIG.$section.file_path")
download_url=$(uci -q get "$CONFIG.$section.download_url")
if [ -n "$download_url" ] && [ -n "$file_path" ]; then
if [ ! -f "$file_path" ]; then
echo "Downloading $section data from $download_url to $file_path..." >&2
wget -q -O "$file_path" "$download_url" --timeout=10 --no-check-certificate
[ $? -ne 0 ] && { echo "Failed to download $section data" >&2; rm -f "$file_path"; }
fi
fi
if [ -n "$file_path" ] && [ -f "$file_path" ]; then
local file_elems=$(get_file_elements "$file_path")
if [ -n "$file_elems" ]; then
[ -n "$elems" ] && elems="${elems}, "
elems="${elems}${file_elems}"
fi
fi
case "$type" in "ipv4_addr"|"inet_service"|"ether_addr") is_interval=1 ;; *) is_interval=0 ;; esac
[ "$is_interval" = "0" ] && { case "$elems" in */*|*-*) is_interval=1 ;; esac; }
printf "\tset %s {\n" "$section"
printf "\t\ttype %s\n" "$type"
[ "$is_interval" = "1" ] && printf "\t\tflags interval\n"
[ -n "$elems" ] && printf "\t\telements = { %s }\n" "$elems"
printf "\t}\n\n"
}
get_file_elements() {
local file_path="$1"
[ ! -f "$file_path" ] && return
if [ "$FLOWPROXY_PREVIEW" = "1" ]; then
local total=$(wc -l < "$file_path")
if [ "$total" -gt 100 ]; then
awk 'BEGIN { count=0 }
!/^($|#)/ {
if (count < 100) {
if (count > 0) {
printf ", "
if (count % 2 == 0) printf "\n\t\t\t "
}
printf "%s", $0
count++
}
}
END { printf ", ... (%d more items omitted for preview)", '"$total"'-100 }' "$file_path"
return
fi
fi
awk 'BEGIN { count=0 }
!/^($|#)/ {
if (count > 0) {
printf ", "
if (count % 2 == 0) printf "\n\t\t\t "
}
printf "%s", $0
count++
}
END { print "" }' "$file_path"
}
process_rule() {
local section="$1"; local proto="$2"
local enabled match_type match_value action counter
enabled=$(uci -q get "$CONFIG.$section.enabled"); [ "$enabled" = "0" ] && { log_debug "Rule $section is disabled, skipping"; return; }
match_type=$(uci -q get "$CONFIG.$section.match_type")
[ -z "$match_type" ] && match_type="dst_ip"
match_value=$(uci -q get "$CONFIG.$section.match_value")
log_debug "Processing $proto rule: $section ($match_type=$match_value)"
action=$(uci -q get "$CONFIG.$section.action"); [ -z "$action" ] && action="return"
counter=$(uci -q get "$CONFIG.$section.counter")
[ -z "$match_value" ] && return
case "$match_value" in
@*)
local set_ref=$(echo "$match_value" | cut -d' ' -f1)
if ! echo "$ENABLED_SETS" | grep -q " ${set_ref} "; then
log_debug "Skipping rule $section: set ${set_ref} is not defined or disabled"
return
fi
;;
esac
match_value=$(echo "$match_value" | sed -E 's/ (return|accept|drop)$//g')
local segment=""
case "$match_type" in
src_mac) segment="ether saddr $match_value ip protocol $proto" ;;
src_ip) segment="ip saddr $match_value ip protocol $proto" ;;
dst_ip) segment="ip daddr $match_value ip protocol $proto" ;;
src_port) segment="ip protocol $proto $proto sport $match_value" ;;
dst_port) segment="ip protocol $proto $proto dport $match_value" ;;
*) segment="$match_value ip protocol $proto" ;;
esac
local line="$segment"; [ "$counter" = "1" ] && line="$line counter"; line="$line $action"
[ -n "$line" ] && echo " $line"
}
SECTIONS_SET=$(uci -q show "$CONFIG" | grep "=nftset" | cut -d'.' -f2 | cut -d'=' -f1)
SECTIONS_TCP=$(uci -q show "$CONFIG" | grep "=tcp_rule" | cut -d'.' -f2 | cut -d'=' -f1)
SECTIONS_UDP=$(uci -q show "$CONFIG" | grep "=udp_rule" | cut -d'.' -f2 | cut -d'=' -f1)
if [ "$1" = "runtime" ]; then
TRAFFIC_MARK=$(uci -q get "$CONFIG.global.traffic_mark" || echo "0x666")
ROUTING_TABLE=$(uci -q get "$CONFIG.global.routing_table" || echo "888")
echo "--- [ nftables live rules ] ---"
nft list table inet "$CONFIG" 2>/dev/null | awk 'BEGIN { count=0; skip=0 }
/elements = \{/ { print $0; skip=1; next }
skip == 1 && /\}/ { printf "\t\t\t ... (items truncated for preview)\n"; print $0; skip=0; next }
skip == 1 { count++; if (count < 20) print $0; next }
{ print $0 }' || echo "(table not found)"
echo ""
echo "--- [ ip rule list ] ---"
ip rule show 2>/dev/null
echo ""
echo "--- [ ip route table $ROUTING_TABLE ] ---"
ip route show table "$ROUTING_TABLE" 2>/dev/null || echo "(table empty)"
exit 0
fi
cat > "$OUTPUT_FILE" << EOF
#!/usr/sbin/nft -f
table inet $NFT_TABLE
flush table inet $NFT_TABLE
table inet $NFT_TABLE {
EOF
for s in $SECTIONS_SET; do
gen_set_definition "$s" >> "$OUTPUT_FILE"
done
TCP_ENABLED=$(uci -q get "$CONFIG.global.tcp_enabled" || echo "1")
UDP_ENABLED=$(uci -q get "$CONFIG.global.udp_enabled" || echo "1")
TRAFFIC_MARK=$(uci -q get "$CONFIG.global.traffic_mark" || echo "0x666")
PROXY_SERVER_IP_ADDR=$(uci -q get "$CONFIG.global.proxy_server_ip_addr")
if [ "$TCP_ENABLED" = "1" ]; then
cat >> "$OUTPUT_FILE" << EOF
chain LAN_MARKFLOW_TCP {
type filter hook prerouting priority mangle; policy accept;
fib daddr type { unspec, local, anycast, multicast } counter return
EOF
if [ -n "$PROXY_SERVER_IP_ADDR" ]; then
echo " ip saddr $PROXY_SERVER_IP_ADDR ip protocol tcp counter return" >> "$OUTPUT_FILE"
fi
for s in $SECTIONS_TCP; do process_rule "$s" "tcp" >> "$OUTPUT_FILE"; done
echo " ip protocol tcp counter meta mark set $TRAFFIC_MARK" >> "$OUTPUT_FILE"
echo " }" >> "$OUTPUT_FILE"
fi
if [ "$UDP_ENABLED" = "1" ]; then
cat >> "$OUTPUT_FILE" << EOF
chain LAN_MARKFLOW_UDP {
type filter hook prerouting priority mangle; policy accept;
fib daddr type { unspec, local, anycast, multicast } counter return
EOF
if [ -n "$PROXY_SERVER_IP_ADDR" ]; then
echo " ip saddr $PROXY_SERVER_IP_ADDR ip protocol udp counter return" >> "$OUTPUT_FILE"
fi
for s in $SECTIONS_UDP; do process_rule "$s" "udp" >> "$OUTPUT_FILE"; done
echo " ip protocol udp counter meta mark set $TRAFFIC_MARK" >> "$OUTPUT_FILE"
echo " }" >> "$OUTPUT_FILE"
fi
echo "}" >> "$OUTPUT_FILE"
cat "$OUTPUT_FILE"
@@ -0,0 +1,11 @@
{
"admin/services/flowproxy": {
"title": "FlowProxy",
"description": "LuCI Support for FlowProxy Traffic Diversion",
"index": 50,
"action": {
"type": "view",
"path": "flowproxy/flowproxy"
}
}
}
@@ -0,0 +1,26 @@
{
"luci-app-flowproxy": {
"description": "Grant access to flowproxy configuration",
"read": {
"uci": [ "flowproxy" ],
"file": {
"/usr/share/flowproxy/*": [ "read" ]
},
"ubus": {
"luci.flowproxy": [ "*" ],
"network.interface": [ "dump" ]
}
},
"write": {
"uci": [ "flowproxy" ],
"file": {
"/usr/bin/wget": [ "exec" ],
"/usr/bin/wc": [ "exec" ],
"/usr/share/flowproxy/generate_nft.sh": [ "exec" ]
},
"ubus": {
"luci.flowproxy": [ "*" ]
}
}
}
}
+175
View File
@@ -0,0 +1,175 @@
'use strict';
import { access, readfile, writefile, stat } from 'fs';
import { cursor } from 'uci';
import { connect } from 'ubus';
const UCI_CONFIG = 'flowproxy';
const STATE_FILE = '/var/run/flowproxy.running';
const IP_CACHE = '/tmp/flowproxy/lan_ip.cache';
const LOG_FILE = '/var/log/flowproxy.log';
const NFT_TABLE = 'flowproxy';
// 辅助函数:安全 trim
function safe_trim(str) {
return (type(str) === 'string') ? trim(str) : '';
}
const methods = {
get_status: {
call: function() {
// 1. 快速探测
let is_running = access(STATE_FILE, 'f');
// 2. 自愈:检查内核状态并同步标记文件
let tcp_active = (system('nft list chain inet ' + NFT_TABLE + ' LAN_MARKFLOW_TCP >/dev/null 2>&1') === 0);
let udp_active = (system('nft list chain inet ' + NFT_TABLE + ' LAN_MARKFLOW_UDP >/dev/null 2>&1') === 0);
let real_running = (tcp_active || udp_active);
if (real_running && !is_running) {
try { writefile(STATE_FILE, ''); } catch(e) {}
} else if (!real_running && is_running) {
try { system('rm -f ' + STATE_FILE); } catch(e) {}
}
// 3. IP 获取 (带缓存)
let lan_ip = readfile(IP_CACHE) || '';
if (!lan_ip) {
try {
let ubus_conn = connect();
if (ubus_conn) {
let ifaces = ubus_conn.call('network.interface', 'dump');
if (type(ifaces?.interface) === 'array') {
// 优先寻找名为 'lan' 的接口,或者寻找拥有 ipv4 地址的第一个非回环接口
let fallback_ip = '';
for (let iface in ifaces.interface) {
if (iface.interface === 'lan' && iface['ipv4-address']?.[0]?.address) {
lan_ip = iface['ipv4-address'][0].address;
break;
}
if (!fallback_ip && iface.interface !== 'loopback' && iface['ipv4-address']?.[0]?.address) {
fallback_ip = iface['ipv4-address'][0].address;
}
}
if (!lan_ip) lan_ip = fallback_ip;
}
if (lan_ip) {
system('mkdir -p /tmp/flowproxy');
writefile(IP_CACHE, lan_ip);
}
ubus_conn.disconnect();
}
} catch (e) {}
}
let uci_cursor = cursor();
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);
return {
running: real_running ? 1 : 0,
dns_running: dns_running,
enabled: int(uci_cursor.get(UCI_CONFIG, 'global', 'enabled') || '0'),
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),
interface: uci_cursor.get(UCI_CONFIG, 'global', 'interface') || 'br-lan',
traffic_mark: uci_cursor.get(UCI_CONFIG, 'global', 'traffic_mark') || '0x666',
nft: { tcp: tcp_active ? 1 : 0, udp: udp_active ? 1 : 0 }
};
}
},
get_logs: {
args: { lines: 100 },
call: function(request) {
let lines = request.args?.lines ?? 100;
let logs = [];
try {
if (access(LOG_FILE, 'r')) {
let log_stat = stat(LOG_FILE);
let content = '';
if (log_stat && log_stat.size > 65536) {
let fd = popen('tail -c 65536 ' + LOG_FILE);
if (fd) {
content = fd.read('all');
fd.close();
}
} else {
content = readfile(LOG_FILE);
}
if (content) {
let log_lines = split(content, "\n");
let total = length(log_lines);
let start = (total > lines) ? (total - lines) : 0;
for (let i = start; i < total; i++) {
if (log_lines[i]) push(logs, log_lines[i]);
}
}
}
} catch (e) {}
return { logs };
}
},
generate_nft_config: {
call: function() {
system('mkdir -p /tmp/flowproxy');
system('FLOWPROXY_PREVIEW=1 /usr/share/flowproxy/generate_nft.sh 2>&1');
return { config: safe_trim(readfile('/tmp/flowproxy/nft.conf') || '') };
}
},
get_runtime_config: {
call: function() {
let res_file = '/tmp/flowproxy/runtime.tmp';
let dns_file = '/tmp/flowproxy/dns_runtime.tmp';
system('mkdir -p /tmp/flowproxy');
system('/usr/share/flowproxy/generate_nft.sh runtime > ' + res_file + ' 2>&1');
let nft_runtime = readfile(res_file) || "(failed)";
system('rm -f ' + res_file);
system('uci show dhcp.@dnsmasq[0] | grep -E "noresolv|dns_redirect|server" | sed -E "s/^dhcp\\.[^.]*\\.([^=]*)=(.*)/\\1: \\2/; s/\\x27//g; s/#/:/g" > ' + dns_file + ' 2>&1');
let dns_out = readfile(dns_file);
system('rm -f ' + dns_file);
let dns_runtime = "\n--- [ DNSmasq Runtime Configuration ] ---\n";
dns_runtime += (dns_out || "(no active dnsmasq overrides)");
return { runtime: safe_trim(nft_runtime + dns_runtime) };
}
},
get_interfaces: {
call: function() {
let interfaces = [];
try {
let ubus_conn = connect();
if (ubus_conn) {
// 使用 network.device status 以获取所有物理设备 (包括 eth0 等 bridge 成员)
let dev_status = ubus_conn.call('network.device', 'status');
if (dev_status) {
for (let name in dev_status) {
push(interfaces, { name: name });
}
}
ubus_conn.disconnect();
}
} catch (e) {}
return { interfaces };
}
},
start_service: { call: function() { return { result: (system('/etc/init.d/flowproxy start >/dev/null 2>&1') === 0) }; } },
stop_service: { call: function() { return { result: (system('/etc/init.d/flowproxy stop >/dev/null 2>&1') === 0) }; } },
restart_service: { call: function() { return { result: (system('/etc/init.d/flowproxy restart >/dev/null 2>&1') === 0) }; } },
clear_logs: { call: function() { try { writefile(LOG_FILE, ''); return { result: true }; } catch(e) { return { result: false }; } } }
};
return { 'luci.flowproxy': methods };
reload_service() {
restart
}