From deef704d420f237d7ff7433c2cc6ed369ea77426 Mon Sep 17 00:00:00 2001 From: Padmalochan Mohapatra Date: Tue, 20 Dec 2022 09:30:24 +0000 Subject: [PATCH] Revert "Revert "qosmngr : standard Linux support via tc+iptables"" --- qosmngr/Makefile | 10 +- .../etc/uci-defaults/60-qos_queue_generate | 43 ++ qosmngr/files/linux/lib/qos/qos.sh | 431 ++++++++++++++++++ 3 files changed, 483 insertions(+), 1 deletion(-) create mode 100644 qosmngr/files/linux/etc/uci-defaults/60-qos_queue_generate create mode 100755 qosmngr/files/linux/lib/qos/qos.sh diff --git a/qosmngr/Makefile b/qosmngr/Makefile index a2be1bb43..c76a01c2e 100644 --- a/qosmngr/Makefile +++ b/qosmngr/Makefile @@ -27,7 +27,7 @@ define Package/qosmngr SECTION:=utils CATEGORY:=Utilities TITLE:=QoS Manager - DEPENDS:=@(TARGET_brcmbca||TARGET_airoha) +libuci +libubox +libubus +libblobmsg-json +libjson-c +libqos + DEPENDS:=@(TARGET_brcmbca||TARGET_airoha||TARGET_iopsys_mediatek) +libuci +libubox +libubus +libblobmsg-json +libjson-c +libqos endef define Package/qosmngr/description @@ -60,4 +60,12 @@ define Package/qosmngr/install $(call Package/qosmngr/install/common,$(1)) endef endif + +ifeq ($(CONFIG_TARGET_iopsys_mediatek),y) +define Package/qosmngr/install + $(CP) ./files/linux/* $(1)/ + echo "Honoring the flag mediatek" + $(call Package/qosmngr/install/common,$(1)) +endef +endif $(eval $(call BuildPackage,qosmngr)) diff --git a/qosmngr/files/linux/etc/uci-defaults/60-qos_queue_generate b/qosmngr/files/linux/etc/uci-defaults/60-qos_queue_generate new file mode 100644 index 000000000..04ba4e27a --- /dev/null +++ b/qosmngr/files/linux/etc/uci-defaults/60-qos_queue_generate @@ -0,0 +1,43 @@ +#!/bin/sh + +. /lib/functions.sh + +ethwan="$(db -q get hw.board.ethernetWanPort)" + +generate_queue(){ + section="$1" + + config_get ifname "$section" "ifname" + + local no_of_q="0 1 2 3 4 5 6 7" + + i=0 + local total_q=$((${no_of_q##* } + 1)) + for i in $no_of_q; do + order=$((total_q - i)) + uci add qos queue + uci rename qos.@queue[-1]="q_${i}_${ifname}" + uci set qos.@queue[-1].enable="1" + uci set qos.@queue[-1].ifname="$ifname" + uci set qos.@queue[-1].precedence="$order" + uci set qos.@queue[-1].scheduling="SP" + uci set qos.@queue[-1].rate="1000000" + uci set qos.@queue[-1].burst_size="1500" + uci set qos.@queue[-1].weight="1" + done + + uci commit qos +} + +if [ -s "/etc/config/qos" ]; then + if uci -q get qos.@queue[0] >/dev/null; then + # return if there is any valid content + exit + else + rm -f /etc/config/qos + fi +fi +touch /etc/config/qos + +config_load ports +config_foreach generate_queue ethport diff --git a/qosmngr/files/linux/lib/qos/qos.sh b/qosmngr/files/linux/lib/qos/qos.sh new file mode 100755 index 000000000..435b7ea31 --- /dev/null +++ b/qosmngr/files/linux/lib/qos/qos.sh @@ -0,0 +1,431 @@ +#!/bin/sh +. /lib/functions.sh +#set -x + +POLICER_COUNT=0 +Q_COUNT=0 +SP_Q_PRIO=7 + +#counter variable to assign classify order value if not added in config +temp_order=1 + +# Function to handle a queue order and +# update total number of queues +handle_q_order() { + local qid="$1" #queue section ID + + config_get is_enable "$qid" "enable" 1 + + # No need to configure disabled queues + if [ $is_enable == '0' ]; then + return + fi + + config_get ifname "$qid" "ifname" + # If ifname is empty that is good enough to break + if [ -z "$ifname" ];then + return + fi + + # Create precedence file containing queue order per + # interface. + local precedence_file="/tmp/qos/$ifname/q_order" + local q_no=$(cat /tmp/qos/$ifname/q_idx) + + config_get precedence "$qid" "precedence" + value=${precedence}_q${q_no} + echo $value >> $precedence_file + + # Update the number of queues per interface. + q_no=$((q_no + 1)) + echo $q_no > /tmp/qos/$ifname/q_idx +} + +# Sort queue, lower value in uci means higher precedence, so this +# function sorts the precedence in decending order +sort_q_by_precedence() { + ifname="$1" + + local order_file="/tmp/qos/$ifname/q_order" + local tmp_order_file="/tmp/qos/$ifname/q_order.tmp" + + sort -n -k1 $order_file > $tmp_order_file + cp $tmp_order_file $order_file + rm -f $tmp_order_file +} + +sort_by_precedence() { + for interf in $(db -q get hw.board.ethernetPortOrder); do + sort_q_by_precedence $interf + done +} + +# function to handle a queue section +handle_queue() { + local qid="$1" #queue section ID + local port="$2" + local port_bw="$3" + local root="$4" + local port_bs="$5" + + config_get is_enable "$qid" "enable" + + # no need to configure disabled queues + if [ "$is_enable" == "0" ]; then + return + fi + + config_get ifname "$qid" "ifname" + # if ifname is empty that is good enough to break + if [ -z "$ifname" ];then + return + fi + + # This is to get the qid per interface. + if [ "$port" != "$ifname" ]; then + return + fi + + local precedence_file="/tmp/qos/$ifname/q_order" + + local temp_order=0 + while read -r line; do + line_qid=${line: -1} + if [ "$line_qid" == "$Q_COUNT" ]; then + break + fi + temp_order=$((temp_order + 1)) + done < "$precedence_file" + + # precedence_file so the order is calculated accordingly. + local order=`expr $SP_Q_PRIO - $temp_order` + + config_get sc_alg "$qid" "scheduling" + config_get wgt "$qid" "weight" 1 + config_get rate "$qid" "rate" + config_get bs "$qid" "burst_size" + config_get qsize "$qid" "queue_size" 1024 + + [ "$rate" == "0" ] && rate="$port_bw" + [ "$bs" == "0" ] && bs="$port_bs" + + local salg=1 + + case "$sc_alg" in + "SP") salg=1 + ;; + "WRR") salg=2 + ;; + "WDRR") salg=3 + ;; + "WFQ") salg=4 + ;; + esac + + # ignore precedence value in case of WRR, broadcom recommends that WRR queue should + # always have precedence value set to 0 + if [ $salg -eq 2 ]; then + order=0 + fi + + if [ $salg -eq 2 ]; then + tc class add dev $port parent ${root}: classid ${root}:$((order + 1)) cbq allot $bs bandwidth ${port_bw}kbit rate ${rate}kbit prio $order weight $wgt avpkt 1500 bounded isolated + else + tc class add dev $port parent ${root}: classid ${root}:$((order + 1)) cbq allot $bs bandwidth ${port_bw}kbit rate ${rate}kbit prio $order avpkt 1500 bounded isolated + fi + + if [ $order -eq 0 ]; then + # By default flowid is targeted to queue 1. + tc filter add dev $port parent ${root}:0 protocol ip prio 1 u32 match u32 0 0 flowid ${root}:1 + fi + + Q_COUNT=$((Q_COUNT + 1)) +} + +#function to handle a policer section +handle_policer() { + local p_sec="$1" # policer section ID + local dir=1 # default direction, upstream + + config_get is_enable "$p_sec" "enable" + + #no need to configure disabled policer + if [ $is_enable == '0' ]; then + return + fi + + POLICER_COUNT=$((POLICER_COUNT + 1)) +} + +setup_qos() { + if [ ! -d "/tmp/qos" ]; then + mkdir -p /tmp/qos + fi + + ebtables -t broute -N qos + ret=$? + if [ $ret -eq 0 ]; then + ebtables -t broute -I BROUTING -j qos + else + ebtables -t broute -D BROUTING -j qos + ebtables -t broute -I BROUTING -j qos + fi + + iptables -w -t mangle -N qos_forward + ret=$? + [ $ret -eq 0 ] && iptables -w -t mangle -I FORWARD -j qos_forward + + iptables -w -t mangle -N qos_prerouting + ret=$? + [ $ret -eq 0 ] && iptables -w -t mangle -I PREROUTING -j qos_prerouting + + iptables -w -t mangle -N qos_output + ret=$? + [ $ret -eq 0 ] && iptables -w -t mangle -I OUTPUT -j qos_output + + ip6tables -t mangle -N qos_forward + ret=$? + [ $ret -eq 0 ] && ip6tables -t mangle -I FORWARD -j qos_forward + + ip6tables -t mangle -N qos_prerouting + ret=$? + [ $ret -eq 0 ] && ip6tables -t mangle -I PREROUTING -j qos_prerouting + + ip6tables -t mangle -N qos_output + ret=$? + [ $ret -eq 0 ] && ip6tables -t mangle -I OUTPUT -j qos_output +} + +handle_policer_rules() { + local c_sec=$1 + local policer_name + local ifname + local pname + local pindex=-1 + local ingress_rate=0 + local in_burst_size=0 + + config_get policer_name "$c_sec" "policer" + if [ -z "$policer_name" ];then + # no need to apply policer if policer not present in this + # classification rule + return + fi + + config_get ifname "$c_sec" "ifname" + if [ -z "$ifname" ]; then + # cannot associate policer as interface is not mentioned + return + fi + + local i=0 + local max_policer_inst=$(cat /tmp/qos/max_policer_inst) + while : + do + if [ $i -eq $max_policer_inst ]; then + break + fi + + pname="$(uci -q get qos.@policer[$i].name)" + if [ "$policer_name" == "$pname" ]; then + pindex=$i + ingress_rate=$(uci -q get qos.@policer[$i].committed_rate) + in_burst_rate=$(uci -q get qos.@policer[$i].committed_burst_size) + break + fi + i=$((i + 1)) + done + + if [ $pindex -lt 0 ]; then + # policer not found, no need to proceed further + return + fi + + config_ingress_rate_limit $ifname $ingress_rate $in_burst_size $pindex + +} + +config_ingress_rate_limit() { + local ifname="$1" + local ingress_rate=$2 + local in_burst_size=$3 + local pindex="$4" + local wanport="$(db -q get hw.board.ethernetWanPort)" + + # Unit in uci file is in bps while that accepted by ethswctl is kbits + if [ $ingress_rate -lt 1000 ]; then + return + fi + + ingress_rate=$((ingress_rate / 1000)) + + if [ $in_burst_size -eq 0 ]; then + in_burst_size=$ingress_rate + else + in_burst_size=$((in_burst_size / 1000)) + fi + + tc qdisc add dev $ifname ingress + + tc filter add dev $ifname parent ffff: protocol ip prio $pindex u32 match ip src 0.0.0.0/0 police rate ${ingress_rate}kbit burst $in_burst_size drop flowid :$pindex + +} + +#function to handle a classify section +handle_classify() { + cid="$1" #classify section ID + + config_get is_enable "$cid" "enable" + # no need to configure disabled classify rules + if [ "$is_enable" == '0' ]; then + return + fi + handle_policer_rules $cid +} + +configure_classify() { + #processing classify section + + # create files that will contain the rules if not present already + mkdir -p /tmp/qos/ + + # Load UCI file + config_load qos + config_foreach handle_classify classify +} + +pre_configure_queue() { + # Delete queues + for intf in $(db get hw.board.ethernetPortOrder); do + rm -rf /tmp/qos/$intf + + mkdir -p /tmp/qos/$intf + touch /tmp/qos/$intf/q_order + touch /tmp/qos/$intf/q_idx + echo 0 > /tmp/qos/$intf/q_idx + + tc qdisc del dev $intf root + tc qdisc del dev $intf ingress + done +} + +get_link_rate() { + intf="$1" + speed=0 + config_load ports + get_speed() { + psid="$1" + iname="$2" + config_load ports + config_get ifname "$psid" "ifname" + if [ "$ifname" == "$iname" ]; then + config_get speed "$psid" "speed" + fi + } + config_foreach get_speed ethport $intf + echo "$speed" +} + +configure_queue() { + qdisc_idx=0 + local bs=1500 + local rate=0 + # Load UCI file + config_load qos + config_foreach handle_q_order queue + sort_by_precedence + + get_intf_shaper_config() { + local b_size + sid="$1" #shaper section ID + + config_get is_enable "$sid" "enable" + # no need to configure disabled queues + if [ "$is_enable" == "0" ]; then + return + fi + + + config_get ifname "$sid" "ifname" + # if ifname is empty that is good enough to break + if [ -z "$ifname" ] || ! [ "$ifname" == "$2" ];then + return + fi + + config_get rate "$sid" "rate" + # Convert the rate from bps to kbps. + if [ $rate -lt 1000 ];then + return + fi + + rate=$(( rate / 1000 )) + config_get b_size "$sid" "burst_size" + if [ "$b_size" == "0" ]; then + bs="$b_size" + fi + } + + for interf in $(db -q get hw.board.ethernetPortOrder); do + Q_COUNT=0 + rate=0 + # sp queue have max priority value = no. of queue configured on the port + # hence read and update SP_Q_PRIO here + local q_no=$(cat /tmp/qos/$interf/q_idx) + SP_Q_PRIO=`expr $q_no - 1` + qdisc_idx=`expr $qdisc_idx + 1` + # link_rate is in mbps and rate is in kbp + link_rate=$(get_link_rate "$interf") + # Read the shaper configuration for interface + config_foreach get_intf_shaper_config shaper $interf + if [ "$rate" == "0" ]; then + rate=$(( link_rate * 1000 )) + fi + + # TODO using 1500 as allot and avpkt, if shaper config exist for interf get burst_size of shaper for actual value + tc qdisc add dev $interf root handle ${qdisc_idx}: cbq allot $bs avpkt 1500 bandwidth ${rate}kbit + + config_foreach handle_queue queue $interf $rate $qdisc_idx $bs + done +} + +configure_policer() { + + # Delete policer + local i=0 + local max_p_inst=0 + if [ -f "/tmp/qos/max_policer_inst" ]; then + max_p_inst=$(cat /tmp/qos/max_policer_inst) + fi + + # reset the policer counter + echo 0 > /tmp/qos/max_policer_inst + # Load UCI file + config_load qos + config_foreach handle_policer policer + echo $POLICER_COUNT > /tmp/qos/max_policer_inst +} + + +configure_qos() { + pre_configure_queue + configure_queue + configure_policer +} + +reload_qos() { + local service_name="$1" + + if [ -z "$service_name" ]; then + configure_qos + elif [ "$service_name" == "queue" ]; then + pre_configure_queue + configure_queue + elif [ "$service_name" == "policer" ]; then + configure_policer + fi +} + +reload_qos_service() { + reload_qos +} +