SourceForge.net Logo

D.11. /etc/init.d/tc

#!/bin/ash
# 
# GPL $Id: tc,v 1.3 2005/10/09 16:39:05 cvonk Exp $
# system init for IPsec server (racoon)

# refer to "Secure Internet Appliance for Small Office / Home Office HOWTO"
# at http://www.cybcon.com/~coert/linux/siso/
#
# based on http://luxik.cdi.cz/~devik/qos/htb/manual/userg.htm

DEV=eth1
IPTABLES="/sbin/iptables"
TC="/usr/sbin/tc"

. /etc/sysconfig/tc.conf
. /etc/init.d/functions

config_section()
{
    section=$1
    insection=0
    cat /etc/sysconfig/tc.conf | while read line ; do
        if [ $insection = 0 ] ; then
	    if [ "$line" = "[$section]" ] ; then
		insection=1
	    fi
	else
	    if [ -z "$line" ] ; then
		return
	    fi
	    echo $line
	fi
    done
}

unconfigure()
{
    # clean existing down- and uplink qdiscs
    $TC qdisc del dev $DEV root    2>/dev/null
    $TC qdisc del dev $DEV ingress 2>/dev/null

    # can't do "$IPTABLES -t mangle -F" because the mangle table might be
    # used for other things (such as IPsec marking for L2TP)
    config_section "classifier" | cut -d# -f1 | grep -v "^$" | \
	while read mark chain rule ; do
	    if [ -n "$chain" ] ; then
		for ch in `echo $chain | sed 's/,/ /g'` ; do
		    $IPTABLES -t mangle -D $ch -o $DEV $rule -j MARK_$mark \
			2>/dev/null
		done
	    fi
    done

    $IPTABLES -t mangle -L -n | grep "^Chain MARK_" | \
	while read c chain rest; do
	    $IPTABLES -t mangle -F $chain 2>/dev/null
            $IPTABLES -t mangle -X $chain 2>/dev/null
    done
}

configure()
{
    # The following queing discipline and its classes will be configured:
    #
    #                    1:                    root qdisc
    #                  (htb)
    #                    |
    #                   1:1                    root class
    #                / //\\  
    #             /  / /  \ \  
    #          /   /  /    \  \   
    #      /     /   /      \   \ 
    #  (0x10)(0x11)(0x12)(0x13)(0x14)          netfilter mark
    #   (0)   (1)   (2)   (3)   (4)            class priority
    #   1:10  1:11  1:12  1:13  1:14           leaf classes
    #    |     |     |     |     |     
    #   10:   11:   12:   13:   14:            qdisc
    #  (sfq) (sfq) (sfq) (sfq) (sfq) 

    # Attach queue discipline HTB to eth1 and name it "handle 1:".  Handles 
    # are written as x:y, where x identifies the qdisc, and y indentifies the
    # class belonging to that qdisc.  (Note that "1:" is treated as "1:0".)

    log "uplink shaping, root class: rate=${UPLINK}kbit ceil=${UPLINK}kbit burst=${BURST}k"

    # r2q was needed to prevent quantum of class .. is small warnings.  The
    # default r2q is 10.  The following condition must be true:
    #  MTU (1500) < rate in Byte / r2q < 60000
    $TC qdisc add dev $DEV root handle 1: htb default 14 r2q 1

    # Creates a "root" class, 1:1 under the qdisc 1:.  (It is a root class,
    # because it has the HTB qdisc as its parent.) 

    # A root class (like other classes under an HTB qdisc), allows its leaves
    # to use each others unused bandwidth, but one root class cannot use 
    # unused bandwith from from another.  Hence we do not need to specify the
    # ceiling for root classes.

    $TC class add dev $DEV parent 1: classid 1:1 \
	htb rate ${UPLINK}kbit burst ${BURST}k

    log "uplink shaping, leave classes and filters"

    # create the leaf classes, filters that classify the packets based on
    # netfilter markings, and attach a simple sfq queueing disappleane to
    # each leaf class

    totalguaranteed=0
    config_section "shaper" | cut -d# -f1 | grep -v "^$" |\
	while read mark classid prio guaranteed ceiling rest ; do

	if [ -n "$classid" ] ; then
	    if [ "$guaranteed" = "-" ] ; then
		guaranteed=$((100-$totalguaranteed));
	    else
		totalguaranteed=$(($totalguaranteed+$guaranteed))
	    fi
	    RATE=$(($guaranteed*$UPLINK/100))
	    if [ "$ceiling" = "-" ] ; then
		CEIL=${UPLINK}
	    else
		CEIL=$(($ceiling*$UPLINK/100))
	    fi
	    log " leaf $classid: rate=${RATE}kbit ceil=${CEIL}kbit burst=${BURST}k" 

	    # create a leaf class

	    $TC class add dev $DEV parent 1:1 classid $classid htb \
		rate ${RATE}kbit \
		ceil ${CEIL}kbit \
		burst ${BURST}k \
		prio $prio

            # Set the filters so we can classify the packets with iptables. 
            # Using iptables (instead of advanced "tc filter" rules) has the
	    # benefit of keeps packet counts for each rule.

	    $TC filter add dev $DEV parent 1:0 protocol ip \
		prio $prio handle $mark fw flowid $classid

            # Attach queuing disciplines to the leaf classes so that bandwidth
	    # sharing is more fair. If none is specified the default is pfifo.

	    QDISCLEAF=`echo $classid | cut -d: -f2`
	    $TC qdisc add dev $DEV parent $classid \
		handle $QDISCLEAF: sfq perturb 10
	fi
    done

    # for the classifier we use netfilter markings

    log "uplink classifier"

    config_section "classifier" | cut -d# -f1 | grep -v "^$" | \
	while read mark chain rule ; do
	    if [ -n "$chain" ] ; then
		if ! $IPTABLES -t mangle -L -n | grep -q "^Chain MARK_$mark" ; then
		    $IPTABLES -t mangle -N MARK_$mark
		    $IPTABLES -t mangle -A MARK_$mark -j MARK --set-mark $mark
		    $IPTABLES -t mangle -A MARK_$mark -j ACCEPT
		fi
		for ch in `echo $chain | sed 's/,/ /g'` ; do
		    $IPTABLES -t mangle -A $ch -o $DEV $rule -j MARK_$mark
		done
	    fi
    done

    if [ -n "$DOWNLINK" ] ; then
	log "downlink policing rate=${DOWNLINK}kbit burst=${BURST}k .."
	
        # slow downloads down to somewhat less than the real speed  to prevent 
        # queuing at our ISP. Tune to see how high you can set it.
    
	$TC qdisc add dev $DEV handle ffff: ingress
	
        # drop everything that is coming in too fast
    
	$TC filter add dev $DEV parent ffff: protocol ip prio 50 u32 \
	    match ip src 0.0.0.0/0 \
	    police rate ${DOWNLINK}kbit burst ${BURST}k drop flowid :1
    else
	log "downlink policing DISABLED"
    fi
}

status_section()
{
    class=$1
    insection=0
    while read line ; do
        if [ $insection = 0 ] ; then
	    if echo $line | grep "^class htb $class" ; then
		insection=1
	    fi
	else
	    if [ -z "$line" ] ; then
		return
	    fi
	    echo $line
	fi
    done
}

cat_header()
{
    filename=$1
    header=$2

    if [ -s $filename ] ; then
	if [ -n "$header" ] ; then
	    echo "$header:"
	fi
	cat $filename
    fi
}

status()
{
    $TC -s class show dev $DEV | \
	/usr/bin/tcstat -v uplink=${UPLINK}000 -v downlink=${DOWNLINK}000

    MARKS=`config_section "shaper" | cut -d# -f1 | grep -v "^$" | cut -d' ' -f1`

    IN_OUTPUT=/tmp/$$.OUTPUT
    IN_PREROUTING=/tmp/$$.PREROUTING
    IN_POSTROUTING=/tmp/$$.POSTROUTING
    IN_TC=/tmp/$$.TC
    IN_FILTER=/tmp/$$.FILTER

    for class in OUTPUT PREROUTING POSTROUTING ; do
	$IPTABLES -t mangle -L $class -vx 2>&1 > $IN_$class
    done
    $TC -s class show dev $DEV > $IN_TC
    $TC filter show dev $DEV > $IN_FILTER

    OUT=/tmp/$$
    for mark in $MARKS ; do
	rm -f $OUT 2>/dev/null
	CLASSID=`grep "handle $mark" $IN_FILTER | cut -d' ' -f12`
	for class in OUTPUT PREROUTING POSTROUTING ; do
	    grep "MARK_$mark" $IN_$class | sed 's/MARK set 0x.*$//g' |
		awk " { printf(\"    %-11s %10u bytes %8u pkt, prot %4s,\", 
                               \"$class\", \$2, \$1, \$4 );
                                    for( ii=10 ; ii <= NF ; ii++) {
                                       printf(\" %s\", \$ii);
                                    }
                                    printf(\"\n\");
                                  }" >> $OUT
	done
	cat_header $OUT "$mark"

	cat $IN_TC | status_section $CLASSID | \
	    grep -v "^tokens" | grep -v "^lended" | sed 's/^/    /' > $OUT

	cat_header $OUT ""
	rm $OUT
    done

    rm -f $IN_OUTPUT $IN_PREROUTING $IN_POSTROUTING $IN_TC
}


#
# main
#

case "$1" in
    start)
	echo -n " $DEV"
	configure
        ;;
    stop)
	unconfigure
        ;;
    restart)
	$0 stop
	$0 start
	;;
    status)
	status
        ;;
esac

# 

Example D.11. /etc/init.d/tc