#!/bin/bash
# Wonder Shaper
#
# Set the following values to somewhat less than your actual download
# and uplink speed. In kilobits. Also set the device that is to be shaped.
#
# License GPLv2 <https://gnu.org/licenses/gpl-2.0.html>
#
# Copyright (c) 2002-2018 Bert Hubert <ahu@ds9a.nl>,
#         Jacco Geul <jacco@geul.net>, Simon Séhier <simon@sehier.fr>
#
# See the ChangeLog for information on the individual contributions of the authors.
# Modified: Doug Hughes <dhughes@starfishstorage.com>
# Date: 2019-09-24
# Base version 1.4
# Description:
# added a -l option to indicate local line speed in gbits/sec
# added exclusions for local RFC1918 address blocks (full bandwidth
# made the default bandwidth shaper at full line speed with -l and -u as sub-classes
# added a default source-port/destport for low priority queuing for 4443 and 443
#   this, for instance, affects outbound to the internet AWS traffic among other
#   web services uplaods
# ***NO SUPPORT OR WARRANTY, IMPLIED OR EXPLICIT ***


QUANTUM=6000
# BURST=15k
COMMONOPTIONS="$COMMONOPTIONS"
VERSION=1.4.1

usage()
{
echo "This is an example of modifying free wondershaper script"
echo "in order to support rate limited uploads to AWS from your Starfish"
echo "instance or agent. It tries to provide exceptions for what"
echo "is normally considered internal traffic using RFC1918 style addresses."
echo "If you have legit ICANN or other real/routable IPS, you'll have to"
echo "add them to the exceptions list and play around with things."
echo "Also, the math isn't very precise for computing bandwidth limits"
echo "based upon priority queueing."
echo ""
echo "depedencies: Linux tc"
echo ""

echo ""
cat << EOF
USAGE: $0 [-hcs] [-a <adapter>] [-d <rate>] [-u <rate>] [-l <rate>]

Limit the bandwidth of an adapter

OPTIONS:
   -h           Show this message
   -a <adapter> Set the adapter
   -d <rate>    Set maximum download rate (in Kbps) and/or
   -u <rate>    Set maximum upload rate (in Kbps)
   -p           Use presets in /etc/conf.d/wondershaper.conf
   -c           Clear the limits from adapter
   -l <rate>    lan local speed override (in Gbps)
   -s           Show the current status of adapter
   -v           Show the current version

MODES:
   wondershaper -a <adapter> -d <rate> -u <rate> [ -l <rate> ]
   wondershaper -c -a <adapter>
   wondershaper -s -a <adapter>

EXAMPLES:
   wondershaper -a eth0 -d 1024 -u 512 -l 10
   wondershaper -a eth0 -u 512
   wondershaper -c -a eth0

EOF
echo ""
}

DSPEED=
USPEED=
LSPEED=1
IFACE=
IFB="ifb0"
MODE=

while getopts hvd:u:a:l:pcs o
do	case "$o" in
	h)	usage
		exit 1;;
        v)      echo "Version $VERSION"
                exit 0;;
	d)	DSPEED=$OPTARG;;
	u)      USPEED=$OPTARG;;
	a)      IFACE=$OPTARG;;
	l)      LSPEED=$OPTARG;;
	p)      MODE="presets";;
	c)      MODE="clear";;
	s)      MODE="status";;
	[?])	usage
		exit 1;;
	esac
done

if [ "$MODE" = "presets" ]
then
    if [ -f /etc/conf.d/wondershaper.conf ]
    then
	source /etc/conf.d/wondershaper.conf
    else
	echo "/etc/conf.d/wondershaper.conf not found"
	exit 1
    fi
fi

if [[ ! -z "$MODE" ]] && [[ -z "$IFACE" ]]
then
    echo "Please supply the adapter name for the mode."
    echo ""
    usage
    exit 1
fi

if [ "$MODE" = "status" ]
then
    tc -s qdisc ls dev "$IFACE"
    tc -s class ls dev "$IFACE"
    exit
fi

if [ "$MODE" = "clear" ]
then
    tc qdisc del dev "$IFACE" root    2> /dev/null > /dev/null
    tc qdisc del dev "$IFACE" ingress 2> /dev/null > /dev/null
    tc qdisc del dev "$IFB"   root    2> /dev/null > /dev/null
    tc qdisc del dev "$IFB"   ingress 2> /dev/null > /dev/null
    exit
fi

if ( [[ -z "$DSPEED" ]] && [[ -z "$USPEED" ]] ) || [[ -z "$IFACE" ]]
then
    usage
    exit 1
fi

# high priority OUTGOING destination traffic (local network) -- full speed
HIPRIOHOSTDST="10.0.0.0/8 192.168.0.0/16 172.16.0.0/12"

# low priority OUTGOING traffic - you can leave this blank if you want
# low priority source netmasks
NOPRIOHOSTSRC=

# low priority destination netmasks
NOPRIOHOSTDST=

# low priority source ports
NOPRIOPORTSRC=

# low priority destination ports
NOPRIOPORTDST="443 4443"

###### uplink

# install root HTB

tc qdisc add dev "$IFACE" root handle 1: htb \
    default 20

# shape everything at $USPEED speed - this prevents huge queues in your
# DSL modem which destroy latency:
# main class
if [[ ! -z $USPEED ]]
then

    #tc class add dev $IFACE parent 1: classid 1:1 htb \
    #    rate ${USPEED}kbit \
    #    prio 5 "$COMMONOPTIONS"
    # shellcheck disable=SC2007
    tc class add dev "$IFACE" parent 1: classid 1:1 htb \
        rate "${LSPEED}gbit" \
        prio 5 "$COMMONOPTIONS"

    # high prio class 1:10:

    #tc class add dev "$IFACE" parent 1:1 classid 1:10 htb \
    #    rate $[80*$USPEED/100]kbit ceil $[95*$USPEED/100]kbit \
    #    prio 1 "$COMMONOPTIONS"
    # shellcheck disable=SC2007
    tc class add dev "$IFACE" parent 1:1 classid 1:10 htb \
        rate $[800*$LSPEED] ceil $[950*$LSPEED]mbit \
        prio 1 "$COMMONOPTIONS"

    # bulk and default class 1:20 - gets slightly less traffic,
    #  and a lower priority:

    # shellcheck disable=SC2007
    tc class add dev "$IFACE" parent 1:1 classid 1:20 htb \
        rate $[90*$USPEED/100]kbit ceil $[100*$USPEED/100]kbit \
        prio 2 "$COMMONOPTIONS"

    # 'traffic we hate'

    # shellcheck disable=SC2007
    tc class add dev "$IFACE" parent 1:1 classid 1:30 htb \
        rate $[80*$USPEED/100]kbit ceil $[90*$USPEED/100]kbit \
        prio 3 "$COMMONOPTIONS"

    # all get Stochastic Fairness:
    tc qdisc add dev "$IFACE" parent 1:10 handle 10: sfq perturb 10 quantum $QUANTUM
    tc qdisc add dev "$IFACE" parent 1:20 handle 20: sfq perturb 10 quantum $QUANTUM
    tc qdisc add dev "$IFACE" parent 1:30 handle 30: sfq perturb 10 quantum $QUANTUM

    # start filters
    # TOS Minimum Delay (ssh, NOT scp) in 1:10:
    tc filter add dev "$IFACE" parent 1: protocol ip prio 10 u32 \
        match ip tos 0x10 0xff  flowid 1:10

    # ICMP (ip protocol 1) in the interactive class 1:10 so we
    # can do measurements & impress our friends:
    tc filter add dev "$IFACE" parent 1: protocol ip prio 11 u32 \
        match ip protocol 1 0xff flowid 1:10

    # prioritize small packets (<64 bytes)

    tc filter add dev "$IFACE" parent 1: protocol ip prio 12 u32 \
        match ip protocol 6 0xff \
        match u8 0x05 0x0f at 0 \
        match u16 0x0000 0xffc0 at 2 \
        flowid 1:10

    # prioritize local traffic
    for a in $HIPRIOHOSTDST
    do
        tc filter add dev "$IFACE" parent 1: protocol ip prio 13 u32 \
            match ip dst "$a" flowid 1:10
    done

    # some traffic however suffers a worse fate
    for a in $NOPRIOPORTDST
    do
        tc filter add dev "$IFACE" parent 1: protocol ip prio 14 u32 \
            match ip dport "$a" 0xffff flowid 1:30
    done

    for a in $NOPRIOPORTSRC
    do
        tc filter add dev "$IFACE" parent 1: protocol ip prio 15 u32 \
            match ip sport "$a" 0xffff flowid 1:30
    done

    for a in $NOPRIOHOSTSRC
    do
        tc filter add dev "$IFACE" parent 1: protocol ip prio 16 u32 \
            match ip src "$a" flowid 1:30
    done

    for a in $NOPRIOHOSTDST
    do
        tc filter add dev "$IFACE" parent 1: protocol ip prio 17 u32 \
            match ip dst "$a" flowid 1:30
    done

    # rest is 'non-interactive' ie 'bulk' and ends up in 1:20

    tc filter add dev "$IFACE" parent 1: protocol ip prio 18 u32 \
        match ip dst 0.0.0.0/0 flowid 1:20

fi

########## downlink #############
# 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.
# ISPs tend to have *huge* queues to make sure big downloads are fast
#
# attach ingress policer:
if [[ ! -z $DSPEED ]]
then

    # Add the IFB interface
    modprobe ifb numifbs=1
    ip link set dev "$IFB" up

    # Redirect ingress (incoming) to egress ifb0
    tc qdisc add dev "$IFACE" handle ffff: ingress
    tc filter add dev "$IFACE" parent ffff: protocol ip u32 match u32 0 0 \
        action mirred egress redirect dev "$IFB"

    # Add class and rules for virtual
    tc qdisc add dev "$IFB" root handle 2: htb
    # shellcheck disable=SC2086
    tc class add dev "$IFB" parent 2: classid 2:1 htb rate ${DSPEED}kbit

    # Add filter to rule for IP address
    tc filter add dev "$IFB" protocol ip parent 2: prio 1 u32 match ip src 0.0.0.0/0 flowid 2:1

fi
