#!/bin/sh
#
# Network Performance Meter
# Copyright (C) 2009-2022 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: thomas.dreibholz@gmail.com
#

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
