#!/bin/sh
# ==========================================================================
#         _   _      _   ____            __ __  __      _
#        | \ | | ___| |_|  _ \ ___ _ __ / _|  \/  | ___| |_ ___ _ __
#        |  \| |/ _ \ __| |_) / _ \ '__| |_| |\/| |/ _ \ __/ _ \ '__|
#        | |\  |  __/ |_|  __/  __/ |  |  _| |  | |  __/ ||  __/ |
#        |_| \_|\___|\__|_|   \___|_|  |_| |_|  |_|\___|\__\___|_|
#
#                  NetPerfMeter -- Network Performance Meter
#                 Copyright (C) 2009-2026 by Thomas Dreibholz
# ==========================================================================
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# Contact:  dreibh@simula.no
# Homepage: https://www.nntb.no/~dreibh/netperfmeter/

if [ $# -lt 1 ] ; then
   echo >&2 "Usage: $0 id source destination bandwidth_kbit_per_s delay_ms loss_rate error_rate [-interfaces name] [-bidirectional] [-queueType type] [-frameCapacity packets] [-redMinTh threshold] [-redMaxTh threshold] [-redMaxP probability] [-redWq wq]"
   exit 1
fi


# ====== Get parameters =====================================================
ID="$1"
if [ "$2" != "" ] ; then
   NETWORK_SRC="$2"
else
   NETWORK_SRC="0.0.0.0/0"
fi
if [ "$3" != "" ] ; then
   NETWORK_DST="$3"
else
   NETWORK_DST="0.0.0.0/0"
fi
INTERFACES=""
QUEUE_TYPE=red
FRAME_CAPACITY=80
RED_MINTH=20
RED_MAXTH=80
RED_MAXP=0.02
RED_WQ=0.002
BANDWIDTH=""
DELAY=""
LOSS=""
ERROR=""
addRule=0
bidirectionalQoS=0
if [ $# -ge 4 ] ; then
   if [ $# -lt 8 ] ; then
     echo >&2 "ERROR: Too few arguments!"
     exit 1
   fi
   if [ "$4" != "" ] ; then
     BANDWIDTH="$4"
     addRule=1
   fi
   if [  "$5" != "" ] ; then
     DELAY="$5"
     addRule=1
   fi
   if [ "$6" != "" ] ; then
     LOSS="$6"
     addRule=1
   fi
   if [ "$7" != "" ] ; then
     ERROR="$7"
     addRule=1
   fi

   shift ; shift ; shift ; shift ; shift ; shift ; shift
   while [ "$1" != "" ] ; do
      if [ "$1" = "-bidirectional" ] ; then
         bidirectionalQoS=1
      elif [ "$1" = "-queueType" ] ; then
         QUEUE_TYPE="$2"
         shift
      elif [ "$1" = "-redMinTh" ] ; then
         RED_MINTH="$2"
         shift
      elif [ "$1" = "-redMaxTh" ] ; then
         RED_MAXTH="$2"
         shift
      elif [ "$1" = "-redMaxP" ] ; then
         RED_MAXP="$2"
         shift
      elif [ "$1" = "-redWq" ] ; then
         RED_WQ="$2"
         shift
      elif [ "$1" = "-frameCapacity" ] ; then
         FRAME_CAPACITY="$2"
         shift
      elif [ "$1" = "-interfaces" ] ; then
         INTERFACES="$2"
         shift
      else
         echo >&2 "ERROR: Invalid option $1!"
         exit 1
      fi
      shift
   done
fi

if [ $addRule -eq 0 ] ; then
   echo "REM"
fi


# ====== Linux QoS setup ====================================================
success=1
SYSTEM=$(uname)
if [ "$SYSTEM" = "Linux" ] ; then
   if [ "$INTERFACES" = "" ] ; then   # Use *all* interfaces
      INTERFACES=$(eval "ifconfig -a" 2>/dev/null | sed -ne 's|^\('"$cur"'[^[:space:][:punct:]]\{1,\}\).*$|\1|p')
   fi

   for iface in $INTERFACES ; do
      tc qdisc del dev "$iface" root 2>/dev/null   # Ohne ID-Angabe wird Root mit jeder ID entfernt!
   done

   if [ $addRule -eq 1 ] ; then
      bandwidthParameter=""
      weightParameter=""
      delayParameter=""
      lossParameter=""
      errorParameter=""
      if [ "$BANDWIDTH" != "" ] ; then
         weightParameter=""
         bandwidthParameter="rate ${BANDWIDTH}Kbit"
      fi
      if [ "$DELAY" != "" ] ; then
         delayParameter="delay ${DELAY}ms"
      fi
      if [ "$LOSS" != "" ] ; then
         lossParameter="loss $(echo "${LOSS}*100.0" | bc)%"
      fi
      if [ "$ERROR" != "" ] ; then
         errorParameter="corrupt $(echo "${ERROR}*100.0" | bc)%"
      fi

      for iface in $INTERFACES ; do
         tc qdisc add dev "$iface" root handle 9"$ID":0 cbq bandwidth 100Mbit avpkt 1500 cell 8 || success=0
         tc class add dev "$iface" parent 9"$ID":0 classid 9"$ID":3 cbq \
            bandwidth 100Mbit "$bandwidthParameter" "$weightParameter" \
            prio 8 allot 1514 cell 8 maxburst 20 avpkt 1500 bounded || success=0

         if [ "$QUEUE_TYPE" = "fifo" ] ; then
            tc qdisc add dev "$iface" handle 8"$ID":0 parent 9"$ID":3 pfifo limit "$FRAME_CAPACITY"
         elif [ "$QUEUE_TYPE" = "red" ] ; then
#             tc qdisc add dev $iface parent 8$ID:0 red limit $FRAME_CAPACITY avpkt 1500
            echo "RED configuration: IMPLEMENT ME!"
            exit 1
         else
            echo >&2 "ERROR: Bad queue type?!"
            exit 1
         fi

         tc filter add dev "$iface" protocol ip parent 9"$ID":0 \
            u32 match ip src "$NETWORK_SRC" match ip dst "$NETWORK_DST" flowid 9"$ID":3 || success=0
         if [ $bidirectionalQoS -eq 1 ] ; then
            tc filter add dev "$iface" protocol ip parent 9"$ID":0 \
               u32 match ip src "$NETWORK_DST" match ip dst "$NETWORK_SRC" flowid 9"$ID":3 || success=0
         fi

         # tc -s qdisc ls dev $iface || success=0
         if [ $success -ne 1 ] ; then
            echo >&2 "ERROR: Failed to configure QDiscs on interface $iface!"
         fi
      done
   fi


# ====== FreeBSD QoS setup ==================================================
elif [ "$SYSTEM" = "FreeBSD" ] ; then
   ID2=$((ID+1))
   /sbin/ipfw "$ID" delete pipe "$ID"  2>/dev/null   # Delete old configuration
   /sbin/ipfw $ID2 delete pipe $ID2 2>/dev/null   # Delete old configuration
   if [ $addRule -eq 1 ] ; then
      if [ "$BANDWIDTH" != "" ] ; then
         BANDWIDTH="bw ${BANDWIDTH}Kbit/s"
      fi
      if [ "$DELAY" != "" ] ; then
         DELAY="delay ${DELAY}ms"
      fi
      if [ "$LOSS" != "" ] ; then
         LOSS="plr ${LOSS}"
      fi
      if [ "$ERROR" != "" ] ; then
         if [ ! "$ERROR" -eq 0 ] ; then
            ERROR="ber ${ERROR}"
         else
            ERROR=""
         fi
      fi

      queueParameters=
      if [ "$QUEUE_TYPE" = "red" ] ; then
         queueParameters="queue $FRAME_CAPACITY red $RED_WQ/$RED_MINTH/$RED_MAXTH/$RED_MAXP"
      elif [ "$QUEUE_TYPE" = "fifo" ] ; then
         queueParameters="queue $FRAME_CAPACITY"
      else
         echo >&2 "ERROR: Invalid queue type $QUEUE_TYPE!"
         exit 1
      fi

      /sbin/ipfw add "$ID" pipe "$ID" out src-ip "$NETWORK_SRC" dst-ip "$NETWORK_DST" || success=0
      /sbin/ipfw pipe "$ID" config "$BANDWIDTH" "$DELAY" "$LOSS" "$ERROR" "$queueParameters" || success=0
      if [ $bidirectionalQoS -eq 1 ] ; then
        /sbin/ipfw add $ID2 pipe $ID2 out src-ip "$NETWORK_DST" dst-ip "$NETWORK_SRC" || success=0
        /sbin/ipfw pipe $ID2 config "$BANDWIDTH" "$DELAY" "$LOSS" "$ERROR" "$queueParameters" || success=0
      fi
   fi


# ====== Other systems ======================================================
else
   echo >&2 "ERROR: Unsupported system $SYSTEM!"
   exit 1
fi


# ====== Check whether QoS configuration has been successful ================
if [ $success -ne 1 ] ; then
   echo >&2 "ERROR: setqos has failed to configure QoS settings!"
   echo >&2 "Command on $(hostname): $0 $@"
   exit 1
fi
