Files
op-packages/luci-app-accesscontrol-plus/root/etc/init.d/miaplus
T

444 lines
14 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/sh /etc/rc.common
#
# Copyright (C) 2015 OpenWrt-dist
#
# This is free software, licensed under the GNU General Public License v3.
# See /LICENSE for more information.
#
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}
}
uci_get_by_name() {
local ret=$(uci get $CONFIG.$1.$2 2>/dev/null)
echo ${ret:=$3}
}
uci_get_by_type() {
local index=0
if [ -n "$4" ]; then
index=$4
fi
local ret=$(uci get $CONFIG.@$1[$index].$2 2>/dev/null)
echo ${ret:=$3}
}
uci_get_mac_by_template(){
local ret=""
for i in $(seq 0 100)
do
local enable=$(uci_get_by_type macbind enable '' $i)
local macaddr=$(uci_get_by_type macbind macaddr '' $i)
local template=$(uci_get_by_type macbind template '' $i)
if [ -z $enable ] || [ -z $macaddr ] || [ -z $template ]; then
break
fi
if [ "$enable" == "1" ]; then
if [ -z "$1" ] || [ "$template" == "$1" ]; then
if [ "$ret" == "" ]; then
ret=$macaddr
else
ret="$ret $macaddr"
fi
fi
fi
done
echo ${ret:=$2}
}
add_rules(){
config_load "$CONFIG"
config_foreach add_template_rule templates
}
add_template_rule(){
local section="$1"
local enable
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
return 0
fi
for macaddr in $macaddrs
do
iptables -t filter -A MIAPLUS -m mac --mac-source $macaddr -j DROP
[ "${ipv6enable:-0}" -eq 1 ] && ip6tables -t filter -A MIAPLUS -m mac --mac-source $macaddr -j DROP
done
for i in $(seq 0 100)
do
local enable=$(uci_get_by_type $1 enable '' $i)
local timeon=$(uci_get_by_type $1 timeon '' $i)
local timeoff=$(uci_get_by_type $1 timeoff '' $i)
local z1=$(uci_get_by_type $1 z1 '' $i)
local z2=$(uci_get_by_type $1 z2 '' $i)
local z3=$(uci_get_by_type $1 z3 '' $i)
local z4=$(uci_get_by_type $1 z4 '' $i)
local z5=$(uci_get_by_type $1 z5 '' $i)
local z6=$(uci_get_by_type $1 z6 '' $i)
local z7=$(uci_get_by_type $1 z7 '' $i)
[ "$z1" == "1" ] && Z1="Mon,"
[ "$z2" == "1" ] && Z2="Tue,"
[ "$z3" == "1" ] && Z3="Wed,"
[ "$z4" == "1" ] && Z4="Thu,"
[ "$z5" == "1" ] && Z5="Fri,"
[ "$z6" == "1" ] && Z6="Sat,"
[ "$z7" == "1" ] && Z7="Sun"
if [ -z $enable ] || [ -z $timeoff ] || [ -z $timeon ]; then
break
fi
if [ "$enable" == "1" ]; then
for macaddr in $macaddrs
do
iptables -t filter -I MIAPLUS -m mac --mac-source $macaddr -m time --kerneltz --timestart $timeon --timestop $timeoff --weekdays $Z1$Z2$Z3$Z4$Z5$Z6$Z7 -j ACCEPT
[ "${ipv6enable:-0}" -eq 1 ] && ip6tables -t filter -I MIAPLUS -m mac --mac-source $macaddr -m time --kerneltz --timestart $timeon --timestop $timeoff --weekdays $Z1$Z2$Z3$Z4$Z5$Z6$Z7 -j ACCEPT
done
fi
for n in $(seq 1 7)
do
unset "Z$n"
done
done
}
start_ipt(){
enable=$(uci -q get miaplus.@basic[0].enable)
[ "${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
iptables -t nat -A PREROUTING -p udp --dport 53 -j REDIRECT --to-ports 53 -m comment --comment "Rule For Control"
iptables -t nat -A PREROUTING -p tcp --dport 53 -j REDIRECT --to-ports 53 -m comment --comment "Rule For Control"
strict=$(uci -q get miaplus.@basic[0].strict)
[ "${strict:-0}" -eq 1 ] && iptables -t filter -I FORWARD -m comment --comment "Rule For Control" -j MIAPLUS
ipv6enable=$(uci -q get miaplus.@basic[0].ipv6enable)
if [ "${ipv6enable:-0}" -eq 1 ]; then
ip6tables -t filter -N MIAPLUS
ip6tables -I INPUT -p udp --dport 53 -m comment --comment "Rule For Control" -j MIAPLUS
ip6tables -I INPUT -p tcp --dport 53 -m comment --comment "Rule For Control" -j MIAPLUS
ip6tables -t nat -A PREROUTING -p udp --dport 53 -j REDIRECT --to-ports 53 -m comment --comment "Rule For Control"
ip6tables -t nat -A PREROUTING -p tcp --dport 53 -j REDIRECT --to-ports 53 -m comment --comment "Rule For Control"
[ "${strict:-0}" -eq 1 ] && ip6tables -t filter -I FORWARD -m comment --comment "Rule For Control" -j MIAPLUS
fi
add_rules
}
status_ipt(){
if iptables -t filter -S MIAPLUS >/dev/null 2>&1; then
echo "running"
return 0
fi
echo "not running"
return 1
}
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
iptables -t nat -D PREROUTING -p udp --dport 53 -j REDIRECT --to-ports 53 -m comment --comment "Rule For Control" 2>/dev/null
iptables -t nat -D PREROUTING -p tcp --dport 53 -j REDIRECT --to-ports 53 -m comment --comment "Rule For Control" 2>/dev/null
iptables -t filter -F MIAPLUS 2>/dev/null
iptables -t filter -X MIAPLUS 2>/dev/null
notfound=$(type ip6tables | grep not)
if [ -z "$notfound" ]; then
ip6tables -t filter -D FORWARD -m comment --comment "Rule For Control" -j MIAPLUS 2>/dev/null
ip6tables -D INPUT -p udp --dport 53 -m comment --comment "Rule For Control" -j MIAPLUS 2>/dev/null
ip6tables -D INPUT -p tcp --dport 53 -m comment --comment "Rule For Control" -j MIAPLUS 2>/dev/null
ip6tables -t nat -D PREROUTING -p udp --dport 53 -j REDIRECT --to-ports 53 -m comment --comment "Rule For Control" 2>/dev/null
ip6tables -t nat -D PREROUTING -p tcp --dport 53 -j REDIRECT --to-ports 53 -m comment --comment "Rule For Control" 2>/dev/null
ip6tables -t filter -F MIAPLUS 2>/dev/null
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
}