#!/usr/bin/bash
#
# HENCSAT QoS Control
# Copyright (C) 2017-2024 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
#

# Ensure running as root
if [ "$(id -u)" != "0" ]; then
  exec sudo "$0" "$@"
fi

CONFIG_FILE="/etc/hencsat/hencsat-router.conf"
LASTSETTINGS_FILE="/etc/hencsat/hencsat-router.last"


# ###### Write log information ##############################################
log ()
{
   echo -e "\x1b[1;34m`date +%FT%H:%M:%S`: $@\x1b[0m"
}


# ###### Write log action information #######################################
log-action ()
{
   echo -en "\x1b[34m`date +%FT%H:%M:%S`: $@\x1b[0m"
}


# ###### Write log result information #######################################
RESULT_GOOD=1
RESULT_BAD=0
_BadResults=0
log-result ()
{
   local result="$1"
   local line=120

   if [ "${result}" = "$RESULT_GOOD" ] ; then
      echo -en "\x1b[${line}G\x1b[32mOK\x1b[0m\n"
   elif [ "${result}" = "$RESULT_BAD" ] ; then
      echo -en "\x1b[${line}G\x1b[30;47;5mFAILED!\x1b[0m\n"
      let _BadResults=$_BadResults+1
   elif [ "${result}" = "" ] ; then
      echo >&2 "ERROR: No result given for log-result?!"
      exit 1
   else
      echo -en "\x1b[${line}G\x1b[0m${result}\n"
   fi
}


# ###### Stop QoS on interface ##############################################
qos-control-stop ()
{
   local interface="$1"
   local baseID="$2"

   local htbID=${baseID}
   local netemID=$((${baseID} + 1))

   log-action "Stopping QoS on ${interface} ..."
   tc qdisc del dev ${interface} root handle ${htbID}: 2>/dev/null
   log-result $RESULT_GOOD

   # tc qdisc ls dev ${interface}
}


# ###### Get QoS statistics of interface ####################################
qos-control-statistics()
{
   local interface="$1"
   local baseID="$2"

   local htbID=${baseID}
   local netemID=$((${baseID} + 1))

   env LANG=C tc -s qdisc show dev ${interface} | \
      grep -A1 "^qdisc netem ${netemID}:" | \
      grep "^ Sent" | \
      awk "{ printf \"${interface}\t%-10s\t%-10s\n\", \$4, \$2 }"
}


# ###### Modify QoS parameters on interface #################################
qos-control-change()
{
   local interface="$1"
   local baseID="$2"      # Use *even* number!
   local bandwidth="$3"   # in Kbit/s
   local delay="$4"       # in ms
   local loss="$5"        # in %
   local error="$6"       # in %

   local htbID=${baseID}
   local netemID=$((${baseID} + 1))

   log-action "Modify ${interface}: $bandwidth Kbit/s, ${delay} ms, ${loss}% loss, ${error}% error"
   tc class change dev ${interface} parent ${htbID}:0 classid ${htbID}:1 htb rate ${bandwidth}kbit burst 10000000 && \
   tc qdisc change dev ${interface} parent ${htbID}:1 handle ${netemID}: netem delay ${delay}ms loss ${loss}% corrupt ${error}% limit 100000 && \
   log-result $RESULT_GOOD || log-result $RESULT_BAD

   tc qdisc ls dev ${interface}
}


# ###### Start QoS on interface #############################################
qos-control-start ()
{
   local interface="$1"
   local baseID="$2"      # Use *even* number!
   local bandwidth="$3"   # in Kbit/s
   local delay="$4"       # in ms
   local loss="$5"        # in %
   local error="$6"       # in %

   local htbID=${baseID}
   local netemID=$((${baseID} + 1))

   log-action "Starting QoS on ${interface} ..."
   tc qdisc del dev ${interface} root handle ${htbID}: 2>/dev/null

   tc qdisc add dev ${interface} root handle ${htbID}: htb default 1 && \
   tc class add dev ${interface} parent ${htbID}:0 classid ${htbID}:1 htb rate ${bandwidth}kbit && \
   tc qdisc add dev ${interface} parent ${htbID}:1 handle ${netemID}: netem && \
   log-result $RESULT_GOOD || log-result $RESULT_BAD

   qos-control-change "${interface}" "${baseID}" "$bandwidth" "${delay}" "${loss}" "${error}"
}



# ###### Main program #######################################################

# ====== Configuration checks ===============================================
if [ $# -lt 1 ] ; then
   echo >&2 "Usage: $0 [[start|change] left_bandwidth_kbit_per_s left_delay_ms left_loss_rate_% left_error_rate_% [right_bandwidth_kbit_per_s right_delay_ms right_loss_rate_% right_error_rate_% [id]]|stats|info|stop]"
   exit 1
fi
if [ ! -e "${CONFIG_FILE}" ] ; then
   echo >&2 "ERROR: Configuration file ${CONFIG_FILE} not found!"
   exit 1
fi

. ${CONFIG_FILE}


# ====== Start or Change ====================================================
action="$1"
if [ "${action}" = "start" -o "${action}" = "change" ] ; then
   if [ $# -eq 5 -o $# -ge 9 ] ; then
      leftBandwidth="$2"    # in Kbit/s
      leftDelay="$3"        # in ms
      leftLoss="$4"         # in %
      leftError="$5"        # in %
      rightBandwidth="$6"   # in Kbit/s
      rightDelay="$7"       # in ms
      rightLoss="$8"        # in %
      rightError="$9"       # in %
      id="${10}"            # String (regexp: [0-9a-Z\.-]+)
      option="${11}"        # -machinereadable
      if [ $# -le 5 ] ; then
         rightBandwidth="${leftBandwidth}"
         rightDelay="${leftDelay}"
         rightLoss="${leftLoss}"
         rightError="${leftError}"
      fi
      if [ "${id}" == "" ] ; then
         id="`uuid`"
      fi
      if [ "${action}" = "start" ] ; then
         qos-control-start "${ROUTER_INTERFACE_LEFT}"   10  "${leftBandwidth}"  "${leftDelay}"  "${leftLoss}"  "${leftError}"
         qos-control-start "${ROUTER_INTERFACE_RIGHT}"  20  "${rightBandwidth}" "${rightDelay}" "${rightLoss}" "${rightError}"
      elif [ "${action}" = "change" ] ; then
         qos-control-change "${ROUTER_INTERFACE_LEFT}"   10  "${leftBandwidth}"  "${leftDelay}"  "${leftLoss}"  "${leftError}"
         qos-control-change "${ROUTER_INTERFACE_RIGHT}"  20  "${rightBandwidth}" "${rightDelay}" "${rightLoss}" "${rightError}"
      fi

      echo -e "${leftBandwidth}\t${leftDelay}\t${leftLoss}\t${leftError}\t${rightBandwidth}\t${rightDelay}\t${rightLoss}\t${rightError}\t${id}" | tee ${LASTSETTINGS_FILE} >/dev/null

      if [ "${action}" = "change" ] ; then
         if [ "${option}" = "-machinereadable" ] ; then
            echo -e "CHANGE\t`cat ${LASTSETTINGS_FILE}`"
         fi
      fi
   else
      echo >&2 "ERROR: Not enough parameters for \"${action}\" given!"
      exit 1
   fi

# ====== Stop ===============================================================
elif [ "${action}" = "stop" ] ; then
   rm -f ${LASTSETTINGS_FILE}
   qos-control-stop "${ROUTER_INTERFACE_LEFT}"  10
   qos-control-stop "${ROUTER_INTERFACE_RIGHT}" 20

# ====== Info ===============================================================
elif [ "${action}" = "info" ] ; then
   if [ -e ${LASTSETTINGS_FILE} ] ; then
      echo -e "INFO\t`cat ${LASTSETTINGS_FILE}`"
   else
      echo -e "INFO\t-1\t-1\t-1\t-1\t-1\t-1\t-1\t-1\t0"
   fi

# ====== Statistics =========================================================
elif [ "${action}" = "stats" ] ; then
   left=`qos-control-statistics "${ROUTER_INTERFACE_LEFT}" 10`
   right=`qos-control-statistics "${ROUTER_INTERFACE_RIGHT}" 20`
   now=`date +%FT%H:%M:%S.%N`
   if [ "$2" = "-machinereadable" ] ; then
      echo -e "STATS\t${now}\tLEFT\t${left}\tRIGHT\t${right}"
      if [ -e ${LASTSETTINGS_FILE} ] ; then
         echo -e "INFO\t`cat ${LASTSETTINGS_FILE}`"
      fi
   else
      log "Statistics:"
      echo -e "time: \t${now}"
      echo -e "left: \t${left}"
      echo -e "right:\t${right}"
   fi

# ====== Error ==============================================================
else
   echo >&2 "ERROR: Unknown command \"${action}\"!"
   exit 1
fi
