mirror of
https://github.com/caiwx86/small-packages.git
synced 2026-07-29 01:41:51 +08:00
379 lines
14 KiB
Bash
379 lines
14 KiB
Bash
#!/bin/bash
|
||
|
||
# Copyright (C) 2006 OpenWrt.org
|
||
# Copyright 2022-2025 sirpdboy <herboy2008@gmail.com>
|
||
|
||
crrun=$1
|
||
crid=$2
|
||
NAME=eqosplus
|
||
IDLIST="/var/$NAME.idlist"
|
||
LOCK="/var/lock/$NAME.lock"
|
||
TMPID="/var/$NAME.tmpid"
|
||
LOG="/var/$NAME_log.log"
|
||
# idlist=`uci show $NAME | grep "enable='1'" | grep "device" | grep -oE '\[.*?\]' | grep -o '[0-9]' `
|
||
|
||
|
||
if [ x$(uci get $NAME.@$NAME[0].ifname 2>/dev/null) = 'x1' ]; then
|
||
ifname=$(uci -q get network.lan.ifname)
|
||
[ "x$ifname" = "x" ] && ifname="device" || ifname="ifname"
|
||
dev=$(uci -q get network.wan.$ifname)
|
||
[ ! "$dev" ] && dev=br-lan
|
||
else
|
||
dev=$(uci -q get $NAME.@$NAME[0].ifname 2>/dev/null)
|
||
[ ! "$dev" ] && dev=br-lan
|
||
fi
|
||
|
||
bin_nft=$(which nft)
|
||
bin_tc=$(which tc)
|
||
bin_ip=$(which ip)
|
||
DEBUG=1
|
||
|
||
nft_type() {
|
||
if command -v nft >/dev/null; then
|
||
nftables_ver="true"
|
||
fi
|
||
}
|
||
|
||
# Debug functions
|
||
dbg_nft() {
|
||
[ "${DEBUG:-0}" -eq 0 ] || echo "DEBUG: nft $*"
|
||
$bin_nft "$@"
|
||
}
|
||
|
||
dbg_tc() {
|
||
[ "${DEBUG:-0}" -eq 0 ] || echo "DEBUG: tc $*"
|
||
$bin_tc "$@"
|
||
}
|
||
|
||
dbg_ip() {
|
||
[ "${DEBUG:-0}" -eq 0 ] || echo "DEBUG: ip $*"
|
||
$bin_ip "$@"
|
||
}
|
||
|
||
is_macaddr() {
|
||
ret=1
|
||
if echo "$1" | grep -qE '^([0-9A-Fa-f]{2}[-:]){5}[0-9A-Fa-f]{2}$'; then
|
||
ret=0
|
||
fi
|
||
return $ret
|
||
}
|
||
|
||
# 数值比较函数
|
||
is_gt_zero() {
|
||
local value=$1
|
||
# 使用bc进行浮点数比较
|
||
echo "$value > 0" | bc -l 2>/dev/null | grep -q 1
|
||
}
|
||
|
||
# 转换为kbit
|
||
to_kbit() {
|
||
local mb=$1
|
||
# MB/s 转 kbit/s: 1 MB/s = 8192 kbit/s
|
||
echo "scale=0; $mb * 8192 / 1" | bc 2>/dev/null || echo "0"
|
||
}
|
||
|
||
# Default commands
|
||
tc="dbg_tc"
|
||
ip="dbg_ip"
|
||
nft="dbg_nft"
|
||
|
||
nft_type
|
||
|
||
stop_qos() {
|
||
for face in $(tc qdisc show 2>/dev/null | grep htb | awk '{print $5}'); do
|
||
$tc qdisc del dev $face root 2>/dev/null
|
||
done
|
||
|
||
$tc qdisc del dev ${dev} root 2>/dev/null
|
||
$tc qdisc del dev ${dev}_ifb root 2>/dev/null
|
||
$tc qdisc del dev ${dev} ingress 2>/dev/null
|
||
$ip link del dev ${dev}_ifb 2>/dev/null 2>&1
|
||
|
||
if [ -n "$nftables_ver" ]; then
|
||
$nft delete table inet ${NAME} 2>/dev/null
|
||
$nft delete table bridge ${NAME} 2>/dev/null
|
||
fi
|
||
|
||
echo "" > "$IDLIST" 2>/dev/null
|
||
}
|
||
|
||
init_qosplus() {
|
||
|
||
insmod sch_htb 2>/dev/null
|
||
insmod act_mirred 2>/dev/null
|
||
insmod ifb 2>/dev/null
|
||
|
||
$ip link del dev ${dev}_ifb 2>/dev/null
|
||
$ip link add dev ${dev}_ifb name ${dev}_ifb type ifb
|
||
$ip link set dev ${dev}_ifb up
|
||
|
||
$tc qdisc del dev ${dev} root 2>/dev/null
|
||
$tc qdisc add dev ${dev} root handle 1:0 htb default 1
|
||
$tc class add dev ${dev} parent 1:0 classid 1:1 htb rate 1000mbit ceil 1000mbit
|
||
|
||
$tc qdisc del dev ${dev}_ifb root 2>/dev/null
|
||
$tc qdisc add dev ${dev}_ifb root handle 1:0 htb default 1
|
||
$tc class add dev ${dev}_ifb parent 1:0 classid 1:1 htb rate 1000mbit ceil 1000mbit
|
||
lanipaddr=$(uci -q get network.lan.ipaddr 2>/dev/null | awk -F '.' '{print $1"."$2"."$3".0/24"}')
|
||
if [ -n "$lanipaddr" ]; then
|
||
$tc filter add dev $dev parent 1:0 protocol ip prio 1 u32 match ip src "$lanipaddr" match ip dst "$lanipaddr" flowid 1:1
|
||
$tc filter add dev ${dev}_ifb parent 1:0 protocol ip prio 1 u32 match ip src "$lanipaddr" match ip dst "$lanipaddr" flowid 1:1
|
||
fi
|
||
$tc qdisc del dev ${dev} ingress 2>/dev/null
|
||
$tc qdisc add dev ${dev} ingress
|
||
$tc filter add dev ${dev} parent ffff: protocol all prio 2 u32 match u32 0 0 flowid 1:1 action mirred egress redirect dev ${dev}_ifb
|
||
|
||
if [ -n "$nftables_ver" ]; then
|
||
$nft delete table inet ${NAME} 2>/dev/null
|
||
$nft delete table bridge ${NAME} 2>/dev/null
|
||
|
||
$nft add table inet ${NAME}
|
||
|
||
$nft add chain inet ${NAME} mark_forward { type filter hook forward priority -200\; policy accept\; }
|
||
$nft add chain inet ${NAME} mark_output { type filter hook output priority -200\; policy accept\; }
|
||
$nft add chain inet ${NAME} mark_postrouting { type filter hook postrouting priority -200\; policy accept\; }
|
||
fi
|
||
|
||
return 0
|
||
}
|
||
|
||
|
||
del_id() {
|
||
local list_id=$1
|
||
id=$((list_id * 10 + 1000))
|
||
[ "${DEBUG:-0}" -eq 0 ] || echo "D: del_id $@ --$id --$mac"
|
||
$tc filter del dev ${dev} parent 1: handle 800::$(printf "%x" $id) 2>/dev/null
|
||
$tc class del dev ${dev} parent 1:1 classid 1:$id 2>/dev/null
|
||
$tc filter del dev ${dev}_ifb parent 1: handle 800::$(printf "%x" $id) 2>/dev/null
|
||
$tc class del dev ${dev}_ifb parent 1:1 classid 1:$id 2>/dev/null
|
||
mac=$(uci -q get $NAME.@device[$list_id].mac 2>/dev/null)
|
||
if [ -n "$nftables_ver" ] && [ -n "$mac" ]; then
|
||
if is_macaddr "$mac"; then
|
||
$nft delete rule inet ${NAME} mark_forward ether saddr $mac counter 2>/dev/null
|
||
$nft delete rule inet ${NAME} mark_output ether saddr $mac counter 2>/dev/null
|
||
$nft delete rule inet ${NAME} mark_postrouting ether saddr $mac counter 2>/dev/null
|
||
else
|
||
Z=$(echo $mac | awk -F '[/]' '{print $2}')
|
||
[ -n "$Z" ] && mac=$(echo $mac | awk -F '[/]' '{print $1}') || Z=32
|
||
$nft delete rule inet ${NAME} mark_forward ip saddr $mac/$Z counter 2>/dev/null
|
||
$nft delete rule inet ${NAME} mark_output ip saddr $mac/$Z counter 2>/dev/null
|
||
$nft delete rule inet ${NAME} mark_postrouting ip saddr $mac/$Z counter 2>/dev/null
|
||
fi
|
||
fi
|
||
}
|
||
|
||
add_mac() {
|
||
local list_id=$1
|
||
id=$((list_id * 10 + 1000))
|
||
M0=$(echo $mac | cut -d : -f 1)$(echo $mac | cut -d : -f 2)
|
||
M1=$(echo $mac | cut -d : -f 3)$(echo $mac | cut -d : -f 4)
|
||
M2=$(echo $mac | cut -d : -f 5)$(echo $mac | cut -d : -f 6)
|
||
[ "${DEBUG:-0}" -eq 0 ] || echo "D: add_mac $@ --id:$id --mac:$mac M012--$M0-$M1-$M2"
|
||
|
||
if is_gt_zero "$UL"; then
|
||
if [ -n "$nftables_ver" ]; then
|
||
$nft add rule inet ${NAME} mark_forward ether saddr $mac counter meta mark set $id
|
||
$nft add rule inet ${NAME} mark_output ether saddr $mac counter meta mark set $id
|
||
$nft add rule inet ${NAME} mark_postrouting ether saddr $mac counter meta mark set $id
|
||
fi
|
||
$tc class add dev ${dev}_ifb parent 1:1 classid 1:$id htb rate "${UL}kbit" ceil "${UL}kbit" prio $id burst 15k cburst 15k
|
||
$tc qdisc add dev ${dev}_ifb parent 1:$id handle ${id}: sfq perturb 10
|
||
|
||
$tc filter add dev ${dev}_ifb parent 1: protocol ip prio $id handle $id fw flowid 1:$id
|
||
|
||
$tc filter add dev ${dev}_ifb parent 1: protocol ip prio $((id + 100)) u32 \
|
||
match u16 0x0800 0xFFFF at -2 \
|
||
match u16 0x${M2} 0xFFFF at -4 \
|
||
match u32 0x${M0}${M1} 0xFFFFFFFF at -8 \
|
||
flowid 1:$id
|
||
fi
|
||
if is_gt_zero "$DL"; then
|
||
$tc class add dev ${dev} parent 1:1 classid 1:$id htb rate "${DL}kbit" ceil "${DL}kbit" prio $id burst 15k cburst 15k
|
||
$tc qdisc add dev ${dev} parent 1:$id handle ${id}: sfq perturb 10
|
||
|
||
$tc filter add dev ${dev} parent 1: protocol ip prio $id u32 \
|
||
match u16 0x0800 0xFFFF at -2 \
|
||
match u32 0x${M1}${M2} 0xFFFFFFFF at -12 \
|
||
match u16 0x${M0} 0xFFFF at -14 \
|
||
flowid 1:$id
|
||
fi
|
||
}
|
||
|
||
add_ip() {
|
||
local list_id=$1
|
||
id=$((list_id * 10 + 1000))
|
||
[ "${DEBUG:-0}" -eq 0 ] || echo "D: add_ip $@ --$id --$mac"
|
||
|
||
Z=$(echo $mac | awk -F '[/]' '{print $2}')
|
||
[ -n "$Z" ] && mac=$(echo $mac | awk -F '[/]' '{print $1}') || Z=32
|
||
if is_gt_zero "$UL"; then
|
||
if [ -n "$nftables_ver" ]; then
|
||
$nft add rule inet ${NAME} mark_forward ip saddr $mac/$Z counter meta mark set $id
|
||
$nft add rule inet ${NAME} mark_output ip saddr $mac/$Z counter meta mark set $id
|
||
$nft add rule inet ${NAME} mark_postrouting ip saddr $mac/$Z counter meta mark set $id
|
||
fi
|
||
$tc class add dev ${dev}_ifb parent 1:1 classid 1:$id htb rate "${UL}kbit" ceil "${UL}kbit" prio $id burst 15k cburst 15k
|
||
$tc qdisc add dev ${dev}_ifb parent 1:$id handle ${id}: sfq perturb 10
|
||
$tc filter add dev ${dev}_ifb parent 1: protocol ip prio $id handle $id fw flowid 1:$id
|
||
$tc filter add dev ${dev}_ifb parent 1:0 prio $((id + 100)) protocol ip u32 \
|
||
match ip src "$mac"/"$Z" \
|
||
flowid 1:$id
|
||
fi
|
||
if is_gt_zero "$DL"; then
|
||
$tc class add dev ${dev} parent 1:1 classid 1:$id htb rate "${DL}kbit" ceil "${DL}kbit" prio $id burst 15k cburst 15k
|
||
$tc qdisc add dev ${dev} parent 1:$id handle ${id}: sfq perturb 10
|
||
$tc filter add dev ${dev} parent 1:0 prio $id protocol ip u32 \
|
||
match ip dst "$mac"/"$Z" \
|
||
flowid 1:$id
|
||
fi
|
||
}
|
||
|
||
check_time() {
|
||
local start=$1
|
||
local end=$2
|
||
local current=$(date +%H%M)
|
||
local start_min=$((10#${start:0:2}*60 + 10#${start:3:2}))
|
||
local end_min=$((10#${end:0:2}*60 + 10#${end:3:2}))
|
||
local current_min=$((10#${current:0:2}*60 + 10#${current:2:2}))
|
||
|
||
if [[ $start_min -lt $end_min ]]; then
|
||
[[ $current_min -ge $start_min && $current_min -lt $end_min ]]
|
||
else
|
||
[[ $current_min -ge $start_min || $current_min -lt $end_min ]]
|
||
fi
|
||
}
|
||
|
||
check_list() {
|
||
local i=$1
|
||
local start_time=$(uci -q get $NAME.@device[$i].timestart 2>/dev/null)
|
||
local end_time=$(uci -q get $NAME.@device[$i].timeend 2>/dev/null)
|
||
local wweek=$(uci -q get $NAME.@device[$i].week 2>/dev/null)
|
||
local current_weekday=$(date +%u)
|
||
[ -z "$start_time" ] && [ -z "$end_time" ] && [ -z "$wweek" ] && return 0
|
||
|
||
if [ -n "$wweek" ] && [ "$wweek" != "0" ]; then
|
||
local day_match=0
|
||
for day in $(echo $wweek | tr ',' ' '); do
|
||
[ "$day" -eq "$current_weekday" ] && day_match=1 && break
|
||
done
|
||
[ "$day_match" -eq 0 ] && return 1
|
||
fi
|
||
|
||
if [ -n "$start_time" ] && [ -n "$end_time" ]; then
|
||
check_time "$start_time" "$end_time" || return 1
|
||
fi
|
||
|
||
return 0
|
||
}
|
||
|
||
case "$crrun" in
|
||
"stop")
|
||
stop_qos
|
||
;;
|
||
"start")
|
||
stop_qos > /dev/null 2>&1
|
||
sleep 2
|
||
init_qosplus
|
||
idlist=$(uci show $NAME 2>/dev/null | grep "enable='1'" | grep "device" | grep -oE '\[.*?\]' | grep -o '[0-9]' | sort -nu | sed -e 's/^/!/g' -e 's/$/!/g' > $IDLIST; cat $IDLIST 2>/dev/null | sed -e 's/!//g')
|
||
|
||
if [ ! -s "$IDLIST" ]; then
|
||
return 1
|
||
fi
|
||
|
||
device_count=0
|
||
for list in $(echo $idlist | sed -e 's/!//g'); do
|
||
if check_list $list; then
|
||
mac=$(uci -q get $NAME.@device[$list].mac 2>/dev/null)
|
||
download_mb=$(uci -q get $NAME.@device[$list].download 2>/dev/null)
|
||
upload_mb=$(uci -q get $NAME.@device[$list].upload 2>/dev/null)
|
||
comment=$(uci -q get $NAME.@device[$list].comment 2>/dev/null)
|
||
|
||
# 转换为kbit
|
||
DL=$(to_kbit "$download_mb")
|
||
UL=$(to_kbit "$upload_mb")
|
||
|
||
if [ -n "$mac" ]; then
|
||
if is_macaddr "$mac"; then
|
||
add_mac $list >>$LOG
|
||
echo "✅ MAC限速: $comment ($mac) - 下载:${download_mb}MB/s(${DL}kbit/s) 上传:${upload_mb}MB/s(${UL}kbit/s)" >>$LOG
|
||
else
|
||
add_ip $list >>$LOG
|
||
echo "✅ IP限速: $comment ($mac) - 下载:${download_mb}MB/s(${DL}kbit/s) 上传:${upload_mb}MB/s(${UL}kbit/s)" >>$LOG
|
||
fi
|
||
device_count=$((device_count + 1))
|
||
fi
|
||
else
|
||
[ -f "$IDLIST" ] && [ $(grep "!${list}!" "$IDLIST" 2>/dev/null | wc -l) -gt 0 ] && {
|
||
del_id $list >>$LOG
|
||
sed -i "/!$list!/d" "$IDLIST" >/dev/null 2>&1
|
||
comment=$(uci -q get $NAME.@device[$list].comment 2>/dev/null)
|
||
}
|
||
fi
|
||
done
|
||
echo "EQOSPLUS限速服务启动完成,共配置 $device_count 个设备" >>$LOG
|
||
echo ""
|
||
echo "当前限速状态:" >>$LOG
|
||
tc -s class show dev $dev | grep "rate" | head -5 >>$LOG
|
||
tc -s class show dev ${dev}_ifb | grep "rate" | head -5 >>$LOG
|
||
|
||
;;
|
||
"add")
|
||
echo "➕ 添加限速规则..."
|
||
for list in $(echo $crid | sed -e 's/!//g' | sed 's/,/ /g'); do
|
||
mac=$(uci -q get $NAME.@device[$list].mac 2>/dev/null)
|
||
download_mb=$(uci -q get $NAME.@device[$list].download 2>/dev/null)
|
||
upload_mb=$(uci -q get $NAME.@device[$list].upload 2>/dev/null)
|
||
comment=$(uci -q get $NAME.@device[$list].comment 2>/dev/null)
|
||
DL=$(to_kbit "$download_mb")
|
||
UL=$(to_kbit "$upload_mb")
|
||
|
||
if is_macaddr $mac; then
|
||
add_mac $list
|
||
echo "✅ 添加MAC限速: $comment ($mac)" >>$LOG
|
||
else
|
||
add_ip $list
|
||
echo "✅ 添加IP限速: $comment ($mac)" >>$LOG
|
||
fi
|
||
done
|
||
;;
|
||
"del")
|
||
for list in $(echo $crid | sed -e 's/!//g' | sed 's/,/ /g'); do
|
||
comment=$(uci -q get $NAME.@device[$list].comment 2>/dev/null)
|
||
mac=$(uci -q get $NAME.@device[$list].mac 2>/dev/null)
|
||
del_id $list
|
||
done
|
||
;;
|
||
"status")
|
||
echo "EQOSPLUS限速服务状态"
|
||
echo "### 网络接口: $dev ###"
|
||
echo "# 下载队列统计 #"
|
||
tc -s qdisc show dev $dev 2>/dev/null || echo "无下载队列"
|
||
echo "# 下载类统计 #"
|
||
tc -s class show dev $dev 2>/dev/null | grep -E "class htb|rate [0-9]" || echo "无下载类"
|
||
echo "# 下载过滤器 #"
|
||
tc -s filter show dev $dev parent 1: 2>/dev/null | head -10 || echo "无下载过滤器"
|
||
|
||
echo "### 虚拟接口: ${dev}_ifb ###"
|
||
echo "# 上传队列统计 #"
|
||
tc -s qdisc show dev ${dev}_ifb 2>/dev/null || echo "无上传队列"
|
||
echo "# 上传类统计 #"
|
||
tc -s class show dev ${dev}_ifb 2>/dev/null | grep -E "class htb|rate [0-9]" || echo "无上传类"
|
||
echo "# 上传过滤器 #"
|
||
tc -s filter show dev ${dev}_ifb parent 1: 2>/dev/null | head -10 || echo "无上传过滤器"
|
||
|
||
if [ -n "$nftables_ver" ]; then
|
||
echo "### NFTables规则 ###"
|
||
nft list table inet ${NAME} 2>/dev/null || echo "没有NFTables规则"
|
||
else
|
||
echo "NFTables不可用"
|
||
fi
|
||
|
||
# 显示流量统计
|
||
echo "### 流量统计 ###"
|
||
echo "下载流量:"
|
||
tc -s class show dev $dev 2>/dev/null | grep "Sent" | head -5
|
||
echo "上传流量:"
|
||
tc -s class show dev ${dev}_ifb 2>/dev/null | grep "Sent" | head -5
|
||
;;
|
||
|
||
esac |