#!/bin/bash # Copyright (C) 2006 OpenWrt.org # Copyright 2022-2025 sirpdboy 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