Thursday, February 23, 2006

Ο τροχονόμος στην γέφυρα: traffic control στο σπιτικό bridged firewall

Στο προηγούμενο τεχνικό ποστ, ο Harry πρόσθεσε σαν σχόλιο ένα script για QoS και fair queeing βασισμένο στον scheduler CBQ. Φυσικά έχει δίκιο πως καμία υπερφορτωμένη γραμμή δεν μπορεί να λειτουργήσει σωστά χωρίς να ρυθμίσουμε τις «προτεραιότητές» μας. Ποια πακέτα δηλαδή πρέπει να «τρέξουν» όσο γίνεται πιο γρήγορα και ποια μπορούν να περιμένουν και λιγάκι πριν φύγουν προς το διαδίκτυο χωρίς να βάλουμε και τα κλάμματα. Προφανώς, ό,τι αφορά interactive χρήση του δικτύου (π.χ. web) πρέπει να έχει προτεραιότητα από τα «κατεβαστήρια», κι αν η γραμμή μας είναι «μπουκωμένη», τα τελευταία θα πρέπει να παραδώσουν όσο bandwidth χρειάζονται τα πρώτα για να μην κοιμηθούμε πριν φορτώσει η σελίδα.

Ένα βασικό θέμα που θα πρέπει να ξεκαθαρίσουμε από την αρχή είναι πως το Quality of Service (QoS) μπορεί να ελέγξει ό,τι βγαίνει από το gateway προς τα έξω και όχι ότι έρχεται από τον έξω κόσμο. Θεωρητικά, μπορούμε να βάλουμε μια QoS discipline και στην εσωτερική NIC, ελέγχοντας τα πακέτα που φεύγουν από τον gateway προς τους τοπικούς υπολογιστές, αυτό όμως σπάνια είναι χρήσιμο καθότι «προς τα μέσα» έχουμε μια σύνδεση στα 100Mbps (Fast Ethernet) ενώ προς τα έξω κάτι πολυ μικρότερο και άρα καθοριστικό στον κορεσμό της επικοινωνίας.

Ένα άλλο σημαντικό σημείο του QoS είναι πως θα πρέπει να περιορίσουμε το συνολικό upload bandwidth ελάχιστα πιο κάτω από το πραγματικό maximum ώστε να ελέγχουμε εμείς το data flow και όχι το επόμενο hop στο δίκτυο του ISP μας.

Το σημαντικό πλεονέκτημα ενός συστήματος QoS, σε αντίθεση με το «κλασσικό» traffic shaping είναι πως ενώ αυτό το τελευταίο καθορίζει στατικά τα όρια χρήσης του bandwidth για κάθε υπολογιστή, το QoS επιτρέπει σε οποιοδήποτε client να πάρει ολόκληρο το εύρος ζώνης υπό την προυπόθεση πως κάτι άλλο πιο «επείγον» δεν χρειάζεται μέρος ή και το 100% αυτής. Το μειονέκτημα είναι πως το κλασσικό traffic shaping μπορεί κάλλιστα να ελέγξει και το inbound traffic, αφού ορίζει συγκεκριμμένα όρια.

Στην περίπτωσή μας, το χειρότερο πρόβλημα ήταν τα φυσικά όρια του router της fastweb που κατακλειζόταν απο open connections του eMule, καθιστώντας αδύνατη άλλη επικοινωνία εάν δεν έβρισκε κάποιο «χρονικό παράθυρο» ανάμεσα στο κλείσιμο μιας σύνδεσης και στο άνοιγμα της επόμενης. Αυτό λύθηκε με το connection limit target του iptables. Αλλά και με τα 500 μόνο connections που ορίσαμε σαν όριο για κάθε client μπορεί να μην φτάνουμε σε κορεσμό του router, φτάνουμε άνετα σε κορεσμό του εύρους ζώνης. Έπρεπε οπωσδήποτε να βάλουμε ένα σύστημα fair queeing αν και τελικά κατασταλάξαμε στους κανόνες μόλις χτες, ύστερα από πολλές δοκιμές. Συμπληρώνοντας λοιπόν το προηγούμενο ποστ, εδώ παρουσιάζω το scriptακι για το QoS που εφαρμόζουμε.

Προτιμήσαμε σαν βασικό discipline το ΗΤΒ καθότι αρκετά «καθαρό» και straight forward, ενώ στο εσωτερικό κάθε «φέτας», το μοίρασμα ανάμεσα στα πακέτα (και κατ' επέκταση ανάμεσα στα client pc) γίνεται από το SFQ. Μην έχοντας έναν εξωφρενικά μεγάλο αριθμό υπολογιστών πίσω από το gateway, αυτός ο συνδυασμός τα καταφέρνει μια χαρά, ενώ παράλληλα είναι αρκετά απλός στον χειρισμό και την εξέλιξή του με το έξτρα πλεονέκτημα πως είναι αρκετά ελαφρύς ώστε να μην «πνίγει» τον κουρασμένο Duron που βρισκεται καταχωνιασμένος στην αποθήκη...

Χωρίσαμε τα πακέτα σε 3 ιδεατές ομάδες: «υπερεπείγοντα», «διαδραστικά» και «μάζα». Στα «διαδραστικά» καταλείγουν όλα τα πακέτα που αφορούν «νορμάλ» χρήσεις του δικτύου και άρα απευθύνονται σε «κλασσικές» destination ports όπως 80, 25 κτλ. Στα υπερεπείγοντα πάνε όλα τα UDP packets καθότι από την μία είναι «υπερευαίσθητα» ως stateless datagramms και από την άλλη είτε ιδιαίτερα μικρά ώστε να μην πνίγουν την γραμμή από μόνα τους κιας έχουν top priority είτε αφορούν κρίσιμα services όπως το VOIP buster, ένα major hit σε τούτο το σπίτι. Στην «μάζα» καταλήγουν όλα τα υπόλοιπα αφού είναι σχεδόν αποκλειστικά p2p traffic. Μια τελευταία πινελιά είναι ο χειρισμός των πακέτων ACK για την διαδραστική κατηγορία ως υπερεπείγοντα, βοηθώντας περεταίρω στην διαδραστικότητα κυρίως του web που διαφορετικά παρουσίαζε ένα αρχικό latency πριν ξεκινήσει μια ορθολογική ταχύτητα στο φόρτωμα της σελίδας. Εδώ ίσως θα έπρεπε να κόψουμε τα «διαδραστικά» σε δύο υπο-ομάδες διαφορετικής μεταχείρισης παρά την απευθείας μεταφορά των ACK στα υπερεπείγοντα (ένα massive web download θα μπορούσε ίσως να προκαλέσει προβλήματα στο VOIP traffic, αυτό θα πρέπει να δοκιμαστεί).

Ορίστε το script:

#!/bin/bash
#(C) 2006 G. Tsarouchas - G. Grammatikopoulos 
#licensed under GPL v.2
#setting variables
UPSPEED=510
OUT_IF=eth1
IN_IF=eth0
BRIDGE_IF=br0
HI=0
NORM=1
LOW=2

#starting up the party
start_tc()
{
#setting root disk, "non otherwise specified" packets end up in the slowest of all discs
tc qdisc add dev $OUT_IF root handle 1: htb default 30
#general orders
tc class add dev $OUT_IF parent 1: classid 1:1 htb rate ${UPSPEED}kbit burst 6k
#hi priority
tc class add dev $OUT_IF parent 1:1 classid 1:10 htb rate $[5*$UPSPEED/10]kbit ceil ${UPSPEED}kbit prio $HI
#normal priority
tc class add dev $OUT_IF parent 1:1 classid 1:20 htb rate $[4*$UPSPEED/10]kbit ceil ${UPSPEED}kbit prio $NORM
#low priority
tc class add dev $OUT_IF parent 1:1 classid 1:30 htb rate $[3*$UPSPEED/10]kbit ceil $[9*$UPSPEED/10]kbit prio $LOW


#fair queeing WITHIN each disc is enforced by SFQ
tc qdisc add dev $OUT_IF parent 1:10 handle 10: sfq perturb 10
tc qdisc add dev $OUT_IF parent 1:20 handle 20: sfq perturb 10
tc qdisc add dev $OUT_IF parent 1:30 handle 30: sfq perturb 10


#filtering the stuff
#"normal" traffic gets the medium treatment
tc filter add dev $OUT_IF protocol ip parent 1:0 prio 1 u32 match ip dport 80 0xffff flowid 1:20
tc filter add dev $OUT_IF protocol ip parent 1:0 prio 1 u32 match ip dport 8080 0xffff flowid 1:20
tc filter add dev $OUT_IF protocol ip parent 1:0 prio 1 u32 match ip dport 143 0xffff flowid 1:20
tc filter add dev $OUT_IF protocol ip parent 1:0 prio 1 u32 match ip dport 465 0xffff flowid 1:20
tc filter add dev $OUT_IF protocol ip parent 1:0 prio 1 u32 match ip dport 25 0xffff flowid 1:20
tc filter add dev $OUT_IF protocol ip parent 1:0 prio 1 u32 match ip dport 443 0xffff flowid 1:20
tc filter add dev $OUT_IF protocol ip parent 1:0 prio 1 u32 match ip dport 110 0xffff flowid 1:20
tc filter add dev $OUT_IF protocol ip parent 1:0 prio 1 u32 match ip dport 993 0xffff flowid 1:20
tc filter add dev $OUT_IF protocol ip parent 1:0 prio 1 u32 match ip dport 995 0xffff flowid 1:20
tc filter add dev $OUT_IF protocol ip parent 1:0 prio 1 u32 match ip dport 5000 0xffff flowid 1:20
tc filter add dev $OUT_IF protocol ip parent 1:0 prio 1 u32 match ip dport 20 0xffff flowid 1:20
tc filter add dev $OUT_IF protocol ip parent 1:0 prio 1 u32 match ip dport 21 0xffff flowid 1:20
tc filter add dev $OUT_IF protocol ip parent 1:0 prio 1 u32 match ip dport 22 0xffff flowid 1:20


#entire UDP protocol traffic gets topmost priority: this includes VOIP buster traffic
tc filter add dev $OUT_IF protocol ip parent 1:0 prio 1 u32 match ip protocol 17 0xff flowid 1:10


#giving topmost priority for ACK packets related to the priviledged 1:20 ports; this should speed up interactive usage even more.
tc filter add dev $OUT_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 match ip dport 80 0xffff flowid 1:10


tc filter add dev $OUT_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 match ip dport 8080 0xffff flowid 1:10

tc filter add dev $OUT_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 match ip dport 143 0xffff flowid 1:10

tc filter add dev $OUT_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 match ip dport 465 0xffff flowid 1:10

tc filter add dev $OUT_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 match ip dport 25 0xffff flowid 1:10

tc filter add dev $OUT_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 match ip dport 443 0xffff flowid 1:10

tc filter add dev $OUT_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 match ip dport 110 0xffff flowid 1:10

tc filter add dev $OUT_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 match ip dport 993 0xffff flowid 1:10

tc filter add dev $OUT_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 match ip dport 995 0xffff flowid 1:10

tc filter add dev $OUT_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 match ip dport 5000 0xffff flowid 1:10

tc filter add dev $OUT_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 match ip dport 20 0xffff flowid 1:10

tc filter add dev $OUT_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 match ip dport 21 0xffff flowid 1:10

tc filter add dev $OUT_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 match ip dport 22 0xffff flowid 1:10
}

#blanking all the tc stuff in one single cute command
stop_tc()
{
/sbin/tc qdisc del dev $OUT_IF root
}

#restarting the whole tc business
restart_tc()
{
stop_tc
start_tc
}

#handling command-line arguments
case "$1" in
start)
echo "Starting traffic control shaping..."
start_tc
;;
stop)
echo "Stopping traffic control shaping..."
stop_tc
;;
restart)
echo "Stopping traffic control shaping..."
stop_tc
echo "Starting traffic control shaping..."
start_tc
;;
*)
echo "Usage: $0 {start|stop|restart}"
;;
esac

1 comment:

Anonymous said...

Τα σέβη μου Κύριε Kitasumba.Πρώτη φορά επισκέπτομαι τα γραπτά σας και τα θεώρησα άξια προσεκτικής ανάγνωσης. Αναμένω λοιπόν πιο πυκνή συνέχεια. Από το Φλεβάρη έχουν περάσει 5 μήνες...