#!/bin/bash
#
#    cbq.init v0.3alpha2
#    Copyright (C) 1999  Pavel Golubev <pg@ksi-linux.com>
#
#    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 2 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, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
#############################################################################

PATH="/bin:/sbin:/usr/bin:/usr/sbin"

CBQ_PATH="/etc/sysconfig/cbq"
CBQ_INIT="/etc/rc.d/cbq.init"
AVPKT=3000

### Function for remove CBQ from all devices
cbq_off () {
	for dev in `ip link|grep ^[0-9]|cut -d ":" -f2`; do
	cbq_device_off
	done
	return
}
### Function for remove root class from device $dev
cbq_device_off () {
	tc qdisc del dev $dev root 2>/dev/null
	return
}

### Check if ip-route is installed
if [ ! -f /sbin/tc -o ! -f /sbin/ip ]; then
	echo "**CBQ: ip-route utilities is not installed!"
	exit
fi


case "$1" in
	start) #### START #####

### Loading modules. 
### If you have cbq,tbf,u32 compiled into kernel - comment it:
for cbqmodule in sch_cbq sch_tbf cls_u32; do
        if ! modprobe $cbqmodule; then
                echo "**CBQ: can't load module $cbqmodule"
                exit
        fi
done


########################################################################
# Get all devices from configuration files $CBQ_PATH/cbq-*             #
# and setup CBQ root classes for them (if it is possible).             #
########################################################################  

### Collect all DEVICE fields from $CBQ_PATH/cbq-*
if [ -n "`ls $CBQ_PATH/cbq-*`" ]; then
	DEVFIELDS=`grep -h ^DEVICE $CBQ_PATH/cbq-*|cut -d "=" -f 2 |\
        sed 's/ //g' | sort -u`
else
	echo "**CBQ: not configured in $CBQ_PATH/ !"
	exit
fi

### Extract all CBQ devices from DEVICE fields in $CBQ_PATH/cbq-*
if [ -z "$DEVFIELDS" ];then
	echo "**CBQ: can't find any DEVICE field in $CBQ_PATH/cbq-*!"
	exit
     else
	DEVICES=`grep -h ^DEVICE $CBQ_PATH/cbq-*|cut -d "=" -f 2 |\
                 sed 's/ //g' | cut -d "," -f 1 | sort -u`
fi

### try to discover interface bandwidth from DEVICE field
### and if OK - setup root class for this one
 
for dev in $DEVICES ; do

### Check for BANDWIDTH and WEIGHT and if correct - setup it
	BANDWIDTH=`echo "$DEVFIELDS"|grep $dev|grep ","|cut -d "," -f2`
	WEIGHT=`echo "$DEVFIELDS"|grep $dev|grep ","|cut -d "," -f3`
	if [ -n "$BANDWIDTH" -a \
             `echo "$BANDWIDTH"|wc -l|sed 's/ //g'` -eq 1 -a \
             -n "$WEIGHT" -a\
             `echo "$WEIGHT"|wc -l|sed 's/ //g'` -eq 1 ]; then
		if [ -z "`ip link|grep "$dev"|grep UP`" ]; then
			echo "**CBQ: can't find device $dev!"
			echo "**CBQ: turned OFF."
			cbq_off
			exit
		fi
### Delete old root class, if present
	cbq_device_off	

### Setup root class for device $dev
	tc qdisc add dev $dev root handle 1`echo $dev|sed 's/[a-z,A-Z]//g'`: \
	cbq bandwidth $BANDWIDTH avpkt $AVPKT cell 8

### Make parent class :1. Every shaper will use it as parent.
	tc class add dev $dev parent 1`echo $dev|sed 's/[a-z,A-Z]//g'`:0 \
	classid 1`echo $dev|sed 's/[a-z,A-Z]//g'`:1 cbq \
	bandwidth $BANDWIDTH rate $BANDWIDTH allot 1514 cell 8 \
	weight $WEIGHT prio 8 maxburst 20 avpkt $AVPKT

	else
		echo "**CBQ: can't discover $dev bandwidth or weight."
		echo "**CBQ: Setup DEVICE field!"
		exit
	fi
done

#######################################################################
# Setting up all classes described in $CBQ_PATH/cbq-*                 #
#######################################################################

### Extract all class ID from filenames in $CBQ_PATH/
CLASSLIST=`cd $CBQ_PATH;ls -1 cbq-*`
for config_cbq in $CLASSLIST; do

	CLASS=`echo "$config_cbq"|cut -d "-" -f 2|cut -d "." -f 1`
	if [ $CLASS -le 1 ]; then
		echo "**CBQ: CLASS in $CBQ_PATH/$config_cbq must be >1 !"
		exit
	fi
	DEVICE=`grep ^DEVICE $CBQ_PATH/$config_cbq| \
                cut -d "=" -f 2|cut -d "," -f 1`
	BANDWIDTH=`echo "$DEVFIELDS"|grep $dev|grep ","|cut -d "," -f2`
	ROOT="1`echo $DEVICE|sed 's/[a-z,A-Z]//g'`"
	CLASSID="$ROOT:$CLASS"
	RATE=`grep ^RATE $CBQ_PATH/$config_cbq| \
              cut -d "=" -f 2`
	WEIGHT=`grep ^WEIGHT $CBQ_PATH/$config_cbq| \
                cut -d "=" -f 2`
	PRIO=`grep ^PRIO $CBQ_PATH/$config_cbq| \
              cut -d "=" -f 2`
	if [ -z "$RATE" -o -z "$WEIGHT" -o -z "$PRIO" ]; then
		echo "**CBQ: Absent RATE,WEIGHT or PRIO field(s)"
		echo "**CBQ: in $CBQ_PATH/$config_cbq !"
		echo "**CBQ: CBQ terminated!"
		cbq_off
		exit
	fi
	BUFFER=`grep ^BUFFER $CBQ_PATH/$config_cbq|\
		cut -d "=" -f 2`
	if [ -z "$BUFFER" ]; then
		BUFFER="10Kb/8"
	fi
	LIMIT=`grep ^LIMIT $CBQ_PATH/$config_cbq|\
		cut -d "=" -f 2`
	if [ -z "$LIMIT" ]; then
	LIMIT="15Kb"
	fi
	RULE=`grep ^RULE $CBQ_PATH/$config_cbq| \
              cut -d "=" -f 2`
### Create class and setup TBF 	
	tc class add dev $DEVICE parent $ROOT:1 classid $CLASSID cbq \
	bandwidth $BANDWIDTH rate $RATE allot 1514 cell 8 weight $WEIGHT \
	prio $PRIO maxburst 20 avpkt $AVPKT bounded

	tc qdisc add dev $DEVICE parent $CLASSID tbf rate $RATE \
	buffer $BUFFER limit $LIMIT

### Create u32 filter for DEST/SOURCE address/port (if RULE field is present)
	if [ -n "$RULE" ]; then
	   for this_rule in $RULE; do
		if [ -n "`echo $this_rule|grep ","`" ]; then
		  SOURCE=`echo $this_rule|cut -d "," -f 1`
		  DEST=`echo $this_rule|cut -d "," -f 2`
		  if [ -n "`echo $SOURCE|grep ":"`" ]; then
		    SADDR=`echo $SOURCE|cut -d ":" -f 1`
		    SPORT=`echo $SOURCE|cut -d ":" -f 2`
		    if [ -n "$SADDR" -a "$SADDR" != "*" ]; then
			if [ -n "$SPORT" -a "$SPORT" != "*" ]; then
			  u32_s="match ip src $SADDR match ip sport $SPORT 0xffff"
			else
			  u32_s="match ip src $SADDR"
			fi
		    else
			if [ -n "$SPORT" -a "$SPORT" != "*" ]; then
			  u32_s="match ip sport $SPORT 0xffff"
			fi
		    fi
		  else
		    if [ -n "$SOURCE" -a "$SOURCE" != "*" ]; then
		    	u32_s="match ip src $SOURCE"
		    else
			u32_s=""
		    fi
		  fi
		  if [ -n "`echo $DEST|grep ":"`" ]; then
		    DADDR=`echo $DEST|cut -d ":" -f 1`
		    DPORT=`echo $DEST|cut -d ":" -f 2`
		    if [ -n "$DADDR" -a "$DADDR" != "*" ]; then
			if [ -n "$DPORT" -a "$DPORT" != "*" ]; then
			  u32_d="match ip dst $DADDR match ip dport $DPORT 0xffff"
			else
			  u32_d="match ip src $DADDR"
			fi
		    else
			if [ -n "$DPORT" -a "$DPORT" != "*" ]; then
			  u32_d="match ip sport $DPORT 0xffff"
			fi
		    fi
		  else
		    if [ -n "$DEST" -a "$DEST" != "*" ]; then
		    	u32_d="match ip dst $DEST"
		    else
			u32_d=""
		    fi
		  fi
		else
		  if [ -n "`echo $this_rule|grep ":"`" ]; then
		    DADDR=`echo $this_rule|cut -d ":" -f 1`
		    DPORT=`echo $this_rule|cut -d ":" -f 2`
		    if [ -n "$DADDR" -a "$DADDR" != "*" ]; then
			if [ -n "$DPORT" -a "$DPORT" != "*" ]; then
			  u32_d="match ip dst $DADDR match ip dport $DPORT 0xffff"
			else
			  u32_d="match ip dst $DADDR"
			fi
		    else
			if [ -n "$DPORT" -a "$DPORT" != "*" ]; then
			  u32_d="match ip dport $DPORT 0xffff"
			fi
		    fi
		  else
		    u32_d="match ip dst $this_rule"
		  fi
		fi

### Uncomment this if you want to see all parsed rules
#		  echo "$u32_s $u32_d"

		  tc filter add dev $DEVICE parent $ROOT:0 protocol ip \
		  prio 100 u32 $u32_s $u32_d flowid $CLASSID

		u32_s="";u32_d=""
	   done
	fi

done
	;;
	timecheck) ### TIMECHECK ###

# Current time in hh:mm format
TIME_NOW=`date +%k:%M`
HOUR_NOW=`date +%k`
MINUTES_NOW=`date +%M`
TOTALMINUTES=`expr $HOUR_NOW \* 60 + $MINUTES_NOW`

# Get list of cbg config files

DEVFIELDS=`grep -h ^DEVICE $CBQ_PATH/cbq-*|cut -d "=" -f 2 |\
           sed 's/ //g' | sort -u`
DEVICES=`grep -h ^DEVICE $CBQ_PATH/cbq-*|cut -d "=" -f 2 |\
           sed 's/ //g' | cut -d "," -f 1 | sort -u`
CLASSLIST=`cd $CBQ_PATH;ls -1 cbq-*`

# Check every cbq config file for TIME parameter
for config_cbq in $CLASSLIST; do
TIMERATE=`grep ^TIME $CBQ_PATH/$config_cbq| \
              cut -d "=" -f 2`

if [ -n "$TIMERATE" ]; then
	if [ `echo "$TIMERATE"|wc -l|sed 's/ //g'` -eq 1 ]; then
	STARTTIME=`echo "$TIMERATE"|cut -d ";" -f1|cut -d "-" -f1`
	STOPTIME=`echo "$TIMERATE"|cut -d ";" -f1|cut -d "-" -f2`
	NEWRATE=`echo "$TIMERATE"|cut -d ";" -f2|cut -d "/" -f1`
	NEWWEIGHT=`echo "$TIMERATE"|cut -d ";" -f2|cut -d "/" -f2`
	STARTHOUR=`echo "$STARTTIME"|cut -d ":" -f1`
	STARTMINUTE=`echo "$STARTTIME"|cut -d ":" -f2`
	START_AT=`expr $STARTHOUR \* 60 + $STARTMINUTE`
	STOPHOUR=`echo "$STOPTIME"|cut -d ":" -f1`
	STOPMINUTE=`echo "$STOPTIME"|cut -d ":" -f2`
	STOP_AT=`expr $STOPHOUR \* 60 + $STOPMINUTE`

	CLASS=`echo "$config_cbq"|cut -d "-" -f 2|cut -d "." -f 1`
	if [ $CLASS -le 1 ]; then
		echo "**CBQ: CLASS in $CBQ_PATH/$config_cbq must be >1 !"
		exit
	fi
	DEVICE=`grep ^DEVICE $CBQ_PATH/$config_cbq| \
                cut -d "=" -f 2|cut -d "," -f 1`
	BANDWIDTH=`echo "$DEVFIELDS"|grep $DEVICE|grep ","|cut -d "," -f2`
	ROOT="1`echo $DEVICE|sed 's/[a-z,A-Z]//g'`"
	CLASSID="$ROOT:$CLASS"
	RATE=`grep ^RATE $CBQ_PATH/$config_cbq| \
              cut -d "=" -f 2`
	WEIGHT=`grep ^WEIGHT $CBQ_PATH/$config_cbq| \
                cut -d "=" -f 2`
	PRIO=`grep ^PRIO $CBQ_PATH/$config_cbq| \
              cut -d "=" -f 2`
	if [ -z "$RATE" -o -z "$WEIGHT" -o -z "$PRIO" ]; then
		echo "**CBQ: Absent RATE,WEIGHT or PRIO field(s)"
		echo "**CBQ: in $CBQ_PATH/$config_cbq !"
		echo "**CBQ: CBQ terminated!"
		cbq_off
		exit
	fi
	BUFFER=`grep ^BUFFER $CBQ_PATH/$config_cbq|\
		cut -d "=" -f 2`
	if [ -z "$BUFFER" ]; then
		BUFFER="10Kb/8"
	fi
	LIMIT=`grep ^LIMIT $CBQ_PATH/$config_cbq|\
		cut -d "=" -f 2`
	if [ -z "$LIMIT" ]; then
	LIMIT="15Kb"
	fi

### Get current RATE. For old iproute version change -f9 to -f7.
RATENOW=`tc class show dev $DEVICE|grep "$CLASSID "|cut -d " " -f9`

	  if [ "$TOTALMINUTES" -ge "$START_AT" \
		-a "$TOTALMINUTES" -le "$STOP_AT" ]; then

### Check current class for RATE and change it (if time allow)
		if [ "$RATENOW" != "$NEWRATE" ]; then
### Change class and TBF for new rate
		tc class replace dev $DEVICE parent $ROOT:1 classid $CLASSID \
		cbq bandwidth $BANDWIDTH rate $NEWRATE allot 1514 cell 8 \
		weight $NEWWEIGHT prio $PRIO maxburst 20 avpkt $AVPKT bounded

		tc qdisc del dev $DEVICE parent $CLASSID tbf rate $RATENOW\
		buffer $BUFFER limit $LIMIT

		tc qdisc add dev $DEVICE parent $CLASSID tbf rate $NEWRATE \
		buffer $BUFFER limit $LIMIT
		echo "$TIME_NOW: Cnange rate from $RATENOW to $NEWRATE"
		fi
	   else
		if [ "$RATENOW" != "$RATE" ]; then
		tc class replace dev $DEVICE parent $ROOT:1 classid $CLASSID \
		cbq bandwidth $BANDWIDTH rate $RATE allot 1514 cell 8 \
		weight $WEIGHT prio $PRIO maxburst 20 avpkt $AVPKT bounded

		tc qdisc del dev $DEVICE parent $CLASSID tbf rate $RATENOW \
		buffer $BUFFER limit $LIMIT

		tc qdisc add dev $DEVICE parent $CLASSID tbf rate $RATE \
		buffer $BUFFER limit $LIMIT
		echo "$TIME_NOW: Cnange rate from $RATENOW to $RATE"
		fi
	   fi
	else
		echo "***CBQ: Sorry but only one TIME is allowed now"
		echo "***CBQ: check $CBQ_PATH/$config_cbq"
	fi
fi
done

	;;
	stop) ### STOP ###
		cbq_off
		;;
	*)
	echo "Usage: cbq.init {start|stop|timecheck}"
esac
