#!/bin/bash
#
# Moritz Orbach 2009
# http://apfelboymchen.net/gnu/configstuff/
# http://apfelboymchen.net/gnu/configstuff/mqos
# http://apfelboymchen.net/gnu/configstuff/mqos.xhtml
#
# http://luxik.cdi.cz/~devik/qos/htb/manual/userg.htm
#

test -f /etc/meins/mqosrc && . /etc/meins/mqosrc || { echo config?
exit 1
}
# kb/s -> kbit
((max *= 8000))
((small_min *= 8000))
((interactive_min *= 8000))
((acks_min *= 8000))
((bulk_max *= 8000))
((bulk_public_min *= 8000))
((bulk_local_p2p_min *= 8000))
((bulk_local_p2p_max *= 8000))

##

((bulk_min = bulk_local_maybe_interactive_min + bulk_local_p2p_min + bulk_public_min))

logger "MQOS!"

##############################
# aufraeumen
tc qdisc del dev $IF root 2>/dev/null

test "$1" = "stop" && exit

###########
# und los #
###########
#                          root (1:0 HTB default 1:13)
#                           |
#                          HTB (1:1)
#                         ////
#    (1:10) interactive -'///
#    (1:11)        ACKS -'//
#    (1:12)       small -'/
#    (1:13)        bulk -'
#                  `- public (1:21)
#                  `- local  (1:22)
#                      `- maybe_interactive (1:30)
#                      `- p2p               (1:31)
#

# queue
# attaches HTB (hierarchical Token Bucket) to $IF and gives it the "handle" 1:
# default class: bulk (1:n). 30 geht noch, 40 net.
tc qdisc add dev $IF root handle 1: htb default 31
# root class
# maximale bandbreite fuer die root class. TODO burst
tc class add dev $IF parent 1: classid 1:1 htb rate $max


#
#     (1:10) interactive -'///
#     (1:11)        ACKS -'//
#     (1:12)       small -'/
#     (1:13)        bulk -'
#
tc class add dev $IF parent 1:1 classid 1:10 htb rate $interactive_min  ceil $max prio 1 quantum 1500
tc class add dev $IF parent 1:1 classid 1:11 htb rate $acks_min         ceil $max prio 2 quantum 1500
tc class add dev $IF parent 1:1 classid 1:12 htb rate $small_min        ceil $max prio 3 quantum 1500
#tc class add dev $IF parent 1:1 classid 1:13 htb rate $bulk_min         ceil $max prio 4 quantum 1500

# queuing disciplines innerhalb der klassen FIXME limit 5 ?
tc qdisc add dev $IF parent 1:10 handle 40: pfifo limit 5
tc qdisc add dev $IF parent 1:11 handle 41: pfifo limit 5
tc qdisc add dev $IF parent 1:12 handle 42: pfifo limit 5

#
#                  `- public (1:21)
#                  `- local  (1:22)
#
tc class add dev $IF parent 1:1 classid 1:21 htb rate $bulk_public_min ceil $max prio 10 quantum 1500
tc qdisc add dev $IF parent 1:21 handle 43: pfifo limit 5
#tc filter add dev $IF parent 1:13 protocol ip prio 1 u32 match ip protocol 6 0xff flowid 1:21

#
#                      `- maybe_interactive (1:30)
#                      `- p2p               (1:31)
#
tc class add dev $IF parent 1:1 classid 1:30 htb rate $bulk_local_maybe_interactive_min ceil $max                prio 20 quantum 1500
tc class add dev $IF parent 1:1 classid 1:31 htb rate $bulk_local_p2p_min               ceil $bulk_local_p2p_max prio 30 quantum 1500
tc qdisc add dev $IF parent 1:30 handle 44: pfifo limit 5

# PRIO fuer verteilung innerhalb maybe_interactive. 50:[123] werden mit der PRIO erstellt.
#tc qdisc add dev $IF parent 1:50 handle 50 prio
#tc qdisc add dev $IF parent 50:1 handle 51: sfq
#tc qdisc add dev $IF parent 50:2 handle 52: tbf rate $(($bulk_max - $bulk_local)) buffer 1600 limit 3000 # XXX magic
#tc qdisc add dev $IF parent 50:3 handle 53: sfq

#
# regeln (pakete verteilen)
#
# laenge des mach-patterns
# u8  0xff
# u16 0xffff
#
# 1000 = 0x8
# 1100 = 0xC
# 1110 = 0xE
#
# at n bestimmt die startposition in *byte*
# neue rules kommen an den *anfang*
# matched eine rule werden keine weiteren mehr durchlaufen
#
# BEISPIELE
# http://tldp.org/HOWTO/Adv-Routing-HOWTO/lartc.adv-filter.u32.html
# http://mike.passwall.com/networking/ippacket.html#TOS
# http://ace-host.stuart.id.au/russell/files/tc/doc/cls_u32.txt
# http://www-net.cs.umass.edu/kurose/transport/ (TODO)
# 
# match u8 0x05 0x0f at 0
# - 8 bit matchen (u8)
# - in byte 1 gucken ob (at 0, "version" und "hdr len")
# - in den letzten 4 bit (0x0F ist 0000 FFFF ist 00001111)
# - der wert 5 steht (0x05/5/0101 = 5)
# -> paket ohne options (der header ist 20 bytes lang)
#    header length ist * 32bit, was einer zeile in der ueblichen darstellung entspricht.
#    Ab zeile 5 kommen die daten (i.e. keine optionen)
#    - options sind selten, und man muss ja wissen wo der TCP-segment beginnt
#
# match u16 0x0000 0xffc0 at 2
# - 16 bit matchen (u16)
# - in byte 3 gucken ob (at 2, "total length")
# - in den ersten 10 bit (1111 1111 1100 0000)
# - der wert 0 steht (0x0000)
# -> trifft nur zu, wenn das paket kleiner 64 byte ist (11 1111 ist 63)
#
# match u8 0x10 0xff at 33
#

# maybe_interactive-ports
for port in 80 443 22 21 873; do
  tc filter add dev $IF protocol ip parent 1: prio 3 u32 \
        match ip protocol 6 0xff    \
        match ip dport $port 0xffff \
        flowid 1:30
done


# public
for port in 80 443; do
  tc filter add dev $IF protocol ip parent 1: prio 3 u32 \
        match ip protocol 6 0xff    \
        match ip sport $port 0xffff \
        flowid 1:21
done

# ESP
tc filter add dev $IF protocol ip parent 1: prio 3 u32 \
        match ip protocol 32 0xff    \
        flowid 1:10

## small
#tc filter add dev $IF protocol ip prio 1 u32 \
#   match ip protocol 6 0xff \
#   match u8 0x05 0x0f at 0 \
#   match u16 0x0000 0xff80 at 2 \
#   flowid 1:12

# interactive dports UDP (17/0x11)
for port in 53 5060; do
tc filter add dev $IF protocol ip parent 1: prio 1 u32 \
    match ip protocol 11 0xff    \
    match ip dport $port 0xffff \
    flowid 1:10
done

# interactive sports UDP (17/0x11)
# match ip protocol 11 0xff    \
for port in 5060 1000{0..4}; do
tc filter add dev $IF protocol ip parent 1: prio 1 u32 \
    match ip sport $port 0xffff \
    flowid 1:10
done

# ICMP
tc filter add dev $IF protocol ip parent 1: prio 1 u32 \
    match ip protocol 1 0xff    \
    flowid 1:12

# ACK < 64 byte (ACK bit set, and no further payload)
# the ACK bit is second older bit (0x10) in the 14-th byte of the TCP header (at nexthdr+13)
#
# IP protocol 6,
# IP header length 0x5 (header length ist in 32bit angegeben).
# IP Total length 0x34 (ACK + 12 bytes of TCP options)
# TCP ack set (bit 5, offset 33)
tc filter add dev $IF parent 1:0 protocol ip prio 1 u32 \
    match ip protocol 6 0xff     \
    match u8 0x05 0x0f at 0      \
    match u16 0x0000 0xffc0 at 2 \
    match u8 0x10 0xff at 33     \
    flowid 1:11

# TOS
# 8765xxxx
# 5. bit ( 16/0x10): minimize delay
# 6. bit ( 32/0x20): Maximize throughput
# 7. bit ( 64/0x40): Maximize reliability
# 8. bit (128/0x80): Minimize monetary costs 
tc filter add dev $IF protocol ip parent 1: prio 2 u32 \
     match ip tos 0x10 0xff \
     flowid 1:10

# interactive ports TCP. Ohne ssh wegen SCP, stattdessen TOS.
for port in 21 5222 5190; do
  tc filter add dev $IF protocol ip parent 1: prio 2 u32 \
    match ip protocol 6 0xff    \
    match ip dport $port 0xffff \
    flowid 1:10
done



# vim:ts=2