mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-31 03:55:13 +08:00
🦄 Sync 2026-08-26 20:42:53
This commit is contained in:
1 parent
53bb25bf38
commit
e7a277e681
487 files changed
+55081
-19590
No files matched your search
@@ -10,8 +10,19 @@ START=30
|
||||
|
||||
CONFIG=miaplus
|
||||
|
||||
# 非 procd 脚本:status 命令不在 rc.common 默认命令表里,需显式注册
|
||||
EXTRA_COMMANDS="status"
|
||||
EXTRA_HELP=" status Show service status (auto fw3/fw4)"
|
||||
|
||||
. /lib/functions.sh
|
||||
|
||||
# 检测当前使用的防火墙框架:fw4 (nftables) 还是 fw3 (iptables)
|
||||
# 注意:fw4 与 fw3 共用 /etc/config/firewall,不存在 /etc/config/fw4,
|
||||
# 只能靠 fw4 / nft 命令是否存在来判定。
|
||||
have_fw4() {
|
||||
command -v fw4 >/dev/null 2>&1 && command -v nft >/dev/null 2>&1
|
||||
}
|
||||
|
||||
uci_export_section_name() {
|
||||
local ret=$(uci -n export $CONFIG | grep "config $1" | awk '{print $3}' | awk -F\' '{print $2}')
|
||||
echo ${ret:=$2}
|
||||
@@ -62,11 +73,23 @@ add_template_rule(){
|
||||
config_get enable "$section" enable 0
|
||||
[ "$enable" = "1" ] && add_rule "$section"
|
||||
}
|
||||
# 分发到具体实现
|
||||
add_rule(){
|
||||
if have_fw4; then
|
||||
add_rule_nft "$1"
|
||||
else
|
||||
add_rule_ipt "$1"
|
||||
fi
|
||||
}
|
||||
|
||||
# =====================================================================
|
||||
# fw3 (iptables / ip6tables)
|
||||
# =====================================================================
|
||||
add_rule_ipt(){
|
||||
ipv6enable=$(uci -q get miaplus.@basic[0].ipv6enable)
|
||||
local macaddrs=$(uci_get_mac_by_template $1 '')
|
||||
if [ -z "$macaddrs" ]; then
|
||||
exit 0
|
||||
return 0
|
||||
fi
|
||||
for macaddr in $macaddrs
|
||||
do
|
||||
@@ -110,21 +133,9 @@ add_rule(){
|
||||
done
|
||||
}
|
||||
|
||||
del_rule(){
|
||||
type=$1
|
||||
blackMacAdd=$(iptables -t nat -L $type | grep -w RETURN | grep -w "MAC" | awk '{print $7}')
|
||||
[ -n "$blackMacAdd" ] && {
|
||||
for macaddrb in $blackMacAdd
|
||||
do
|
||||
iptables -t nat -D $type -m mac --mac-source $macaddrb -j RETURN
|
||||
done
|
||||
}
|
||||
}
|
||||
|
||||
start(){
|
||||
stop
|
||||
start_ipt(){
|
||||
enable=$(uci -q get miaplus.@basic[0].enable)
|
||||
[ "${enable:-0}" -eq 0 ] && exit 0
|
||||
[ "${enable:-0}" -eq 0 ] && return 0
|
||||
iptables -t filter -N MIAPLUS
|
||||
iptables -I INPUT -p udp --dport 53 -m comment --comment "Rule For Control" -j MIAPLUS
|
||||
iptables -I INPUT -p tcp --dport 53 -m comment --comment "Rule For Control" -j MIAPLUS
|
||||
@@ -144,7 +155,7 @@ start(){
|
||||
fi
|
||||
add_rules
|
||||
}
|
||||
status(){
|
||||
status_ipt(){
|
||||
if iptables -t filter -S MIAPLUS >/dev/null 2>&1; then
|
||||
echo "running"
|
||||
return 0
|
||||
@@ -153,7 +164,7 @@ status(){
|
||||
echo "not running"
|
||||
return 1
|
||||
}
|
||||
stop(){
|
||||
stop_ipt(){
|
||||
iptables -t filter -D FORWARD -m comment --comment "Rule For Control" -j MIAPLUS 2>/dev/null
|
||||
iptables -D INPUT -p udp --dport 53 -m comment --comment "Rule For Control" -j MIAPLUS 2>/dev/null
|
||||
iptables -D INPUT -p tcp --dport 53 -m comment --comment "Rule For Control" -j MIAPLUS 2>/dev/null
|
||||
@@ -173,3 +184,260 @@ stop(){
|
||||
ip6tables -t filter -X MIAPLUS 2>/dev/null
|
||||
fi
|
||||
}
|
||||
|
||||
# =====================================================================
|
||||
# fw4 (nftables)
|
||||
# 使用独立表 inet miaplus,避免与 fw4 自身的 nftables 规则冲突。
|
||||
#
|
||||
# table inet miaplus {
|
||||
# chain input { type filter hook input priority 0; } # 仅匹配 53 端口
|
||||
# chain prerouting{ type nat hook prerouting priority dstnat; } # DNS 重定向到本机
|
||||
# }
|
||||
#
|
||||
# DNS 语义与 fw3 一致:只对发往 53 端口的流量做上网控制。
|
||||
#
|
||||
# NAT 竞争说明:本表 prerouting 链与 fw4 主表(inet fw4)的 PREROUTING/dstnat 同为
|
||||
# priority -100(dstnat)。nf_tables 允许多表同优先级共存,但同一数据包的同一 hook
|
||||
# 优先级内按表加载顺序处理,conntrack binding(redirect/redirect)先到先得。若 fw4
|
||||
# 自身对 DNS(53) 也做了重定向/端口转发,两者可能互抢,现象以先加载者为准。
|
||||
# 一般情况下 DNS 默认直通本机,二者不冲突;如遇规则不生效,优先排查此项。
|
||||
# =====================================================================
|
||||
|
||||
# 构造 nft 的星期集合片段,如 "{ 1, 2 }" / "5" / 空(不限制)
|
||||
# z1..z7 => 周一..周日;nft meta day: 0=Sun,1=Mon,...6=Sat => z1->1 ...
|
||||
nft_dayterm() {
|
||||
local days="" n=1 day
|
||||
for z in "$1" "$2" "$3" "$4" "$5" "$6" "$7"; do
|
||||
[ "$z" = "1" ] || { n=$((n+1)); continue; }
|
||||
day=$(( n % 7 )) # z1..z7 -> 1,2,3,4,5,6,0
|
||||
days="${days:+$days, }$day"
|
||||
n=$((n+1))
|
||||
done
|
||||
if [ -z "$days" ]; then
|
||||
echo ""
|
||||
elif echo "$days" | grep -q ,; then
|
||||
echo "{ $days }"
|
||||
else
|
||||
echo "$days"
|
||||
fi
|
||||
}
|
||||
|
||||
# 去掉前导零(防空串转成 "0"),规避 08/09 被当八进制的算术陷阱
|
||||
_digits() {
|
||||
echo "$1" | sed -e 's/^0*//' -e 's/^$/0/'
|
||||
}
|
||||
# HH:MM -> 分钟数(输入可为 H:M 或 HH:MM,非零填充)
|
||||
_mins() {
|
||||
local h m
|
||||
h=$(_digits "${1%%:*}")
|
||||
m=$(_digits "${1#*:}")
|
||||
echo $(( ${h:-0} * 60 + ${m:-0} ))
|
||||
}
|
||||
# 任意 "H:M"/"HH:MM" -> 规范 "HH:MM"
|
||||
_pad() {
|
||||
local h m
|
||||
h=$(_digits "${1%%:*}")
|
||||
m=$(_digits "${1#*:}")
|
||||
printf '%02d:%02d' "${h:-0}" "${m:-0}"
|
||||
}
|
||||
|
||||
# 把一个 [t_on, t_off] 窗口(H:M/HH:MM)转成 nft 时间匹配表达式(行),
|
||||
# 输出到全局变量 NFT_FRAGS。nft 的 meta hour 原生支持 "HH:MM"-"HH:MM"。
|
||||
# 统一语义:结束分钟视为完整包含(闭区间含其末秒),故上界一律补 :59 —
|
||||
# 非跨午夜 [t_on, t_off] -> "HH:MM"-"t_off:59";跨午夜拆 [t_on,23:59:59]+[00:00,t_off:59]。
|
||||
nft_split_window() {
|
||||
local t_on t_off
|
||||
NFT_FRAGS=""
|
||||
[ -n "$1" ] && [ -n "$2" ] || return 0
|
||||
t_on=$(_pad "$1"); t_off=$(_pad "$2")
|
||||
|
||||
if [ "$(_mins "$t_on")" -le "$(_mins "$t_off")" ]; then
|
||||
NFT_FRAGS="meta hour \"$t_on\"-\"$t_off:59\""
|
||||
else
|
||||
# 跨午夜:拆成 [on,23:59:59] + [00:00,off:59]
|
||||
NFT_FRAGS="meta hour \"$t_on\"-\"23:59:59\"
|
||||
meta hour \"00:00\"-\"$t_off:59\""
|
||||
fi
|
||||
}
|
||||
|
||||
# 统一执行 nft 规则命令并检查返回码。
|
||||
# 失败时记日志、立即回滚删表并置全局 NFT_FAIL=1(供调用方中止后续写入),
|
||||
# 使最终状态收敛为"完整规则表"或"空表",避免留下只 DROP 无 ACCEPT 的断网中间态。
|
||||
nft_rule() {
|
||||
# 已失败则短路,避免对已回滚删除的表继续逐条报错刷屏
|
||||
[ -n "$NFT_FAIL" ] && return 1
|
||||
"$@" 2>/dev/null || {
|
||||
logger -t miaplus "nft rule failed: $*"
|
||||
stop_nft
|
||||
NFT_FAIL=1
|
||||
}
|
||||
}
|
||||
|
||||
# 组合规则前缀:链决定是否只限 53 端口;ipv6enable=0 时限定 ipv4
|
||||
nft_prefix() {
|
||||
local chain=$1
|
||||
local pref=""
|
||||
[ "$chain" = "forward" ] || pref="meta l4proto { tcp, udp } th dport 53 "
|
||||
if [ "$ipv6enable" != "1" ]; then
|
||||
pref="meta nfproto ipv4 $pref"
|
||||
fi
|
||||
NFT_PREFIX="$pref"
|
||||
}
|
||||
|
||||
# 插入一条白名单 ACCEPT 规则(insert 到链首,兜底 DROP 在尾)
|
||||
# 参数:MAC day_term [链名=当前]
|
||||
nft_insert_accept() {
|
||||
local mac=$1 day=$2 chain=${3:-input}
|
||||
nft_prefix "$chain"
|
||||
local cond
|
||||
while IFS= read -r frag; do
|
||||
[ -n "$frag" ] || continue
|
||||
cond="$NFT_PREFIX ether saddr $mac $frag"
|
||||
[ -n "$day" ] && cond="$cond meta day $day"
|
||||
nft_rule nft insert rule inet miaplus $chain $cond accept
|
||||
[ -n "$NFT_FAIL" ] && return 1
|
||||
done <<EOF
|
||||
$NFT_FRAGS
|
||||
EOF
|
||||
}
|
||||
|
||||
# 插入一条兜底 DROP 规则(append 到链尾)
|
||||
# 参数:MAC [链名=当前]
|
||||
nft_add_drop() {
|
||||
local mac=$1 chain=${2:-input}
|
||||
nft_prefix "$chain"
|
||||
nft_rule nft add rule inet miaplus $chain $NFT_PREFIX ether saddr $mac drop
|
||||
}
|
||||
|
||||
# 需要写入的链列表:非严格=仅 input;严格=input+forward
|
||||
nft_target_chains() {
|
||||
if [ "${strict:-0}" -eq 1 ]; then
|
||||
echo "input forward"
|
||||
else
|
||||
echo "input"
|
||||
fi
|
||||
}
|
||||
|
||||
add_rule_nft(){
|
||||
local ipv6enable=$(uci -q get miaplus.@basic[0].ipv6enable)
|
||||
local macaddrs=$(uci_get_mac_by_template $1 '')
|
||||
if [ -z "$macaddrs" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
local chains="$(nft_target_chains)"
|
||||
local macaddr i enable timeon timeoff z1 z2 z3 z4 z5 z6 z7
|
||||
|
||||
# 先为每个绑定 MAC 加兜底 DROP(每条链一次,不重复)
|
||||
for macaddr in $macaddrs; do
|
||||
[ -n "$NFT_FAIL" ] && return 1
|
||||
local ch
|
||||
for ch in $chains; do
|
||||
nft_add_drop "$macaddr" "$ch"
|
||||
done
|
||||
done
|
||||
|
||||
# 再按时间段插入 ACCEPT 白名单
|
||||
for i in $(seq 0 100); do
|
||||
[ -n "$NFT_FAIL" ] && return 1
|
||||
enable=$(uci_get_by_type $1 enable '' $i)
|
||||
timeon=$(uci_get_by_type $1 timeon '' $i)
|
||||
timeoff=$(uci_get_by_type $1 timeoff '' $i)
|
||||
z1=$(uci_get_by_type $1 z1 '' $i)
|
||||
z2=$(uci_get_by_type $1 z2 '' $i)
|
||||
z3=$(uci_get_by_type $1 z3 '' $i)
|
||||
z4=$(uci_get_by_type $1 z4 '' $i)
|
||||
z5=$(uci_get_by_type $1 z5 '' $i)
|
||||
z6=$(uci_get_by_type $1 z6 '' $i)
|
||||
z7=$(uci_get_by_type $1 z7 '' $i)
|
||||
|
||||
[ -n "$enable" ] || break
|
||||
[ -n "$timeon" ] || break
|
||||
[ -n "$timeoff" ] || break
|
||||
[ "$enable" = "1" ] || continue
|
||||
|
||||
local dayspec=$(nft_dayterm "$z1" "$z2" "$z3" "$z4" "$z5" "$z6" "$z7")
|
||||
|
||||
nft_split_window "$timeon" "$timeoff"
|
||||
for macaddr in $macaddrs; do
|
||||
local ch
|
||||
for ch in $chains; do
|
||||
nft_insert_accept "$macaddr" "$dayspec" "$ch"
|
||||
done
|
||||
done
|
||||
done
|
||||
}
|
||||
|
||||
start_nft(){
|
||||
enable=$(uci -q get miaplus.@basic[0].enable)
|
||||
[ "${enable:-0}" -eq 0 ] && return 0
|
||||
strict=$(uci -q get miaplus.@basic[0].strict)
|
||||
local ipv6enable=$(uci -q get miaplus.@basic[0].ipv6enable)
|
||||
|
||||
NFT_FAIL=""
|
||||
|
||||
# 建表/建链也走 nft_rule,使最根本的"建表/建链失败"同样记日志并可回滚
|
||||
nft_rule nft add table inet miaplus
|
||||
nft_rule nft add chain inet miaplus input '{ type filter hook input priority 0; policy accept; }'
|
||||
nft_rule nft add chain inet miaplus prerouting '{ type nat hook prerouting priority dstnat; policy accept; }'
|
||||
# DNS 劫持遵循 ipv6enable:关闭时仅重定向 IPv4 的 53,与 filter 链/DROP 语义对齐
|
||||
if [ "${ipv6enable:-0}" -eq 1 ]; then
|
||||
nft_rule nft add rule inet miaplus prerouting meta l4proto { tcp, udp } th dport 53 redirect to :53
|
||||
else
|
||||
nft_rule nft add rule inet miaplus prerouting meta nfproto ipv4 meta l4proto { tcp, udp } th dport 53 redirect to :53
|
||||
fi
|
||||
[ -n "$NFT_FAIL" ] && return 1
|
||||
|
||||
# 严格模式:额外对 FORWARD 全流量做同样控制
|
||||
if [ "${strict:-0}" -eq 1 ]; then
|
||||
nft_rule nft add chain inet miaplus forward '{ type filter hook forward priority 0; policy accept; }'
|
||||
[ -n "$NFT_FAIL" ] && return 1
|
||||
fi
|
||||
|
||||
add_rules
|
||||
|
||||
# 任一规则写入失败:回滚整个表,避免留下只有 DROP、无 ACCEPT 白名单的断网状态
|
||||
if [ -n "$NFT_FAIL" ]; then
|
||||
logger -t miaplus "fw4 rules incomplete, rolling back"
|
||||
stop_nft
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
status_nft(){
|
||||
if nft list table inet miaplus >/dev/null 2>&1; then
|
||||
echo "running"
|
||||
return 0
|
||||
fi
|
||||
echo "not running"
|
||||
return 1
|
||||
}
|
||||
stop_nft(){
|
||||
nft delete table inet miaplus 2>/dev/null
|
||||
return 0
|
||||
}
|
||||
|
||||
# =====================================================================
|
||||
# 统一入口
|
||||
# =====================================================================
|
||||
start(){
|
||||
stop
|
||||
if have_fw4; then
|
||||
start_nft
|
||||
else
|
||||
start_ipt
|
||||
fi
|
||||
}
|
||||
status(){
|
||||
if have_fw4; then
|
||||
status_nft
|
||||
else
|
||||
status_ipt
|
||||
fi
|
||||
}
|
||||
stop(){
|
||||
if have_fw4; then
|
||||
stop_nft
|
||||
else
|
||||
stop_ipt
|
||||
fi
|
||||
}
|
||||
@@ -8,6 +8,8 @@
|
||||
EEOF
|
||||
}
|
||||
|
||||
# fw4 与 fw3 共用 /etc/config/firewall 的 config include 机制;fw4_compatible
|
||||
# 让 fw4 在 reload 时执行用户脚本,fw3 忽略此未知 option(安全)
|
||||
uci -q batch <<-EOF >/dev/null
|
||||
delete ucitrack.@miaplus[-1]
|
||||
add ucitrack miaplus
|
||||
@@ -18,6 +20,7 @@ uci -q batch <<-EOF >/dev/null
|
||||
set firewall.miaplus.type=script
|
||||
set firewall.miaplus.path=/etc/miaplus.include
|
||||
set firewall.miaplus.reload=1
|
||||
set firewall.miaplus.fw4_compatible=1
|
||||
commit firewall
|
||||
EOF
|
||||
|
||||
|
||||
Reference in new issue
Block a user