#! /bin/bash

set -e
OPTERR=0

intf=""
bw=""
delay="0"
loss="0%"
qlen=""
qlen_factor="1"
path_bw=""
rtt=""
path_mtu=""

fatal()
{
	echo $@ >&2
	exit 1
}

warn()
{
	echo $@ >&2
}

usage()
{
	#     12345678901234567890123456789012345678901234567890123456789012345678901234567890
	echo "usage: netematic -i <interface> [-b <bandwidth limit>] [-d <delay>] [-l <loss>]" >&2
	echo "        [-q <queue length>] [-f <queue length factor>] [-B <path bandwidth>]"    >&2
	echo "        [-R <RTT>] [-M <path MTU>]"                                              >&2
	exit 1
}

strip_units()
{
	input=$1
	unit=$2
	
	units "$input" "$unit" -t -d 15
}

round_up()
{
	# HACK: Round up anything with up to 20 decimals (standard bc -l behaviour)
	echo $1 | awk '{printf("%d\n",$1 + 0.99999999999999999999)}'
}

detect_bw()
{
	speed_line=$(ethtool "$intf" | grep "Speed:")
	if [ "$speed_line" == "" ]
	then
		echo "Unable to detect link bandwidth." 1>&2
		return 1
	fi

	strip_units $(echo "$speed_line" | cut -d ' ' -f 2 | tr '/' 'p') bps
}

detect_mtu()
{
	ip link show $intf | head -1 | cut -d ' ' -f 5
}

ensure_intf()
{
	if [ -z "$intf" ]
	then
		fatal "No interface specified."
	fi
}

ensure_path_bw()
{
	if [ ! -z "$path_bw" ]
	then
		return
	fi
	
	if [ ! -z "$bw" ]
	then
		warn "Assuming a path bandwidth equal to the bandwidth limit."
		path_bw=$bw
	else
		warn "Assuming a path bandwidth equal to the interface's bandwidth."
		path_bw=$(detect_bw)
	fi
}

ensure_rtt()
{
	if [ ! -z "$rtt" ]
	then
		return
	fi
	
	warn "Assuming an RTT equal to twice the delay."
	rtt=$(echo "$delay * 2" | bc)
}

ensure_path_mtu()
{
	if [ ! -z "$path_mtu" ]
	then
		return
	fi
	
	warn "Assuming a path MTU equal to the interface's MTU."
	path_mtu=$(detect_mtu)
}

ensure_qlen()
{
	if [ ! -z "$qlen" ]
	then
		if [ ! -z "$path_bw" ]
		then
			warn "Ignoring path bandwidth."
		fi
		if [ ! -z "$rtt" ]
		then
			warn "Ignoring RTT."
		fi
		if [ ! -z "$path_mtu" ]
		then
			warn "Ignoring path MTU."
		fi
	else
		warn "Using a queue length equal to the BDP."
		
		ensure_path_bw
		ensure_rtt
		ensure_path_mtu
		
		qlen=$(echo "$path_bw * $rtt / 8 / $path_mtu" | bc -l)
	fi
}

ensure_something_to_do()
{
	if [ -z "$bw" ] && [ -z "$delay" ] && [ -z "$loss" ]
	then
		fatal "Nothing to do."
	fi
}

while getopts "i:b:d:l:q:f:B:R:M:" opt
do
	case $opt in
	i)
		intf=$OPTARG
	;;
	b)
		bw=$(strip_units $OPTARG bps)
	;;
	d)
		delay=$(strip_units $OPTARG s)
	;;
	l)
		loss=$OPTARG
	;;
	q)
		qlen=$OPTARG
	;;
	f)
		qlen_factor=$OPTARG
	;;
	B)
		path_bw=$(strip_units $OPTARG bps)
	;;
	R)
		rtt=$(strip_units $OPTARG s)
	;;
	M)
		path_mtu=$OPTARG
	;;
	?)
		usage
	esac
done

ensure_intf
ensure_something_to_do
ensure_qlen

qlen=$(round_up $(echo "$qlen * $qlen_factor" | bc -l))
if [ $qlen -eq 0 ]
then
	fatal "Can not use a queue length equal to 0."
fi

# HACK: There might not be any rules, so skip error checking
set +e
tc qdisc del dev $intf root &>/dev/null
set -e

if [ -z "$bw" ]
then
	tc qdisc add dev $intf root netem delay $delay\s loss $loss limit $qlen
else

	tc qdisc add dev $intf root handle 1:0 htb default 1
	tc class add dev $intf parent 1:0 classid 1:1 htb rate $bw\bit
	tc qdisc add dev $intf parent 1:1 handle 10: netem delay $delay\s loss $loss limit $qlen
fi
