#!/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.device)
    [ "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