HY-335a Project: microtcp, μία lightweight TCP βιβλιοθήκη Deadline πρώτης ϕάσης : 19/11/2017 23:59
Γενική περιγραϕή Στο οικοσύστημα του Internet of Things (IoT) υπάρχει μια ευρεία γκάμα από δικτυακές συσκευές. Πολλές από αυτές έχουν ελάχιστο μέγεθος και λειτουργούν με μπαταρίες. Για αυτόν τον λόγο, οι υπολογιστικοί τους πόροι είναι αρκετά περιορισμένοι. Στις περισσότερες περιπτώσεις, η υλοποίηση ολόκληρου του TCP πρωτοκόλλου είναι αδύνατη και η μεταϕορά της πληροϕορίας γίνεται με UDP. Υπάρχουν όμως εϕαρμογές που απαιτούν αξιόπιστη επικοινωνία και μεταϕορά δεδομένων. Ο σκοπός του project είναι η υλοποίηση μιας βιβλιοθήκης η οποία θα παρέχει μια απλή έκδοση του TCP. Παρόλα αυτά, θα είναι ικανή να παρέχει αξιόπιστη επικοινωνία μεταξύ συσκευών με περιορισμένους υπολογιστικούς πόρους χρησιμοποιώντας το διαθέσιμο UDP πρωτόκολλο μεταϕοράς. 1. Δομή του project - Προθεσμίες To project αποτελείται από 2 ϕάσεις. Στην πρώτη ϕάση ζητείται να υλοποιήσετε την δομή των microtcp πακέτων, το 3-way handshake (χειραψία) το finalize (τερματισμό) της microtcp σύνδεσης καθώς και την απλή μεταϕορά δεδομένων (χωρίς re-transmissions, flow control, κτλ). Επίσης, θα υλοποιήσετε ένα εργαλείο μεταϕοράς αρχείων με TCP το οποίο θα χρησιμοποιήσετε στο τέλος του μαθήματος για την σύγκριση της απόδοσης της υλοποίησή σας, τόσο σε σχέση με την πλήρη υλοποίηση του TCP, όσο και με υλοποιήσεις άλλων ομάδων. Στην δεύτερη ϕάση θα πρέπει να υλοποιήσετε την βασική λειτουργία του TCP η οποία περιλαμβάνει τους μηχανισμούς για acknowledgements, retransmissions, error checking, TCP windowing, Congestion control και Slow start. Η προθεσμία παράδοσης της πρώτης ϕάσης είναι 19 Νοεμβρίου, ενώ η εξέταση αυτής της ϕάσης θα γίνει τις ημέρες 20-21 Νοεμβρίου. Η προθεσμία παράδοσης της δεύτερης ϕάσης είναι 22 Δεκεμβρίου, ενώ η εξέταση αυτής της ϕάσης θα γίνει τις ημέρες 15-16 Ιανουαρίου. Απαγορεύεται να αλλάξετε τα συμβόλαια των συναρτήσεων που σας έχουν δοθεί και το structure του header (microtcp_header_t) που σας δίνονται. Η αλλαγή τους θα σημαίνει και τον μηδενισμό του project. Παρόλα αυτά είναι επιθυμητές οι αλλαγές/προσθήκες στα υπόλοιπα structs. 1
2. Αναλυτική περιγραϕή 2.1 Δομή Κώδικα Στο https://github.com/surligas/microtcp θα βρείτε ένα μέρος έτοιμου κώδικα που θα πρέπει να χρησιμοποιήσετε. Υπάρχουν σχόλια στον κώδικα που αναϕέρουν που ακριβώς πρέπει να κάνετε αλλαγές με δικό σας κώδικα. Ο υπάρχων κώδικας δομείται ως εξής : 1. Φάκελος lib: Περιέχει την δομή και τις συναρτήσεις της microtcp βιβλιοθήκης. 2. Φάκελος test: Περιέχει το εργαλείο μέτρησης του bandwidth τόσο με χρήση της κανονικής TCP υλοποίησης όσο και της microtcp. Επίσης στον ϕάκελο αυτό θα βρείτε έναν έτοιμο traffic generator που θα χρησιμοποιήσετε για πειράματα, καθώς και το αρχείο traffic_generator_client.c του οποίου τον κώδικα θα πρέπει να συμπληρώσετε. Στον ίδιο ϕάκελο θα βρείτε και άλλα δύο αρχεία test_microtcp_server.c και test_microtcp_client.c. Αυτά γίνονται αυτόματα compile από το buid system και μπορείτε να τα χρησιμοποιήσετε για να test κατά την διάρκεια της υλοποίησής σας. 3. Φάκελος utils: Περιέχει χρήσιμες συναρτήσεις. Σε αυτή την ϕάση περιέχει την συνάρτηση υπολογισμού του checksum. Για το compile και την παραγωγή του εκτελέσιμου μπορείτε να χρησιμοποιήσετε το cmake (https://cmake.org/). Το εργαλείο αυτό παράγει αυτόματα τα κατάλληλα makefiles σε ένα ξεχωριστό ϕάκελο ώστε ο ϕάκελος με τον πηγαίο κώδικα να παραμένει καθαρός από ενδιάμεσα αρχεία. Για να γίνει αυτό μέσα στο ϕάκελο με τον source code δημιουργείστε ένα ϕάκελο με το όνομα build. Μέσα σε αυτόν το ϕάκελο τρέξτε την εντολή cmake όπως ϕαίνεται στο παρακάτω παράδειγμα : mkdir build cd build cmake.. Μετά από αυτό μπορείτε να κάνετε compile δίνοντας την εντολή : make Τα παραγόμενα εκτελέσιμα βρίσκονται πλέον μέσα στο directory test του build directory. Κατά την παράδοση της εργασίας σας δεν παραδίδετε τον ϕάκελο build. Το ίδιο πρέπει να κάνετε και όταν ανταλλάσετε κώδικα με τα άλλα άτομα της ομάδας σας. Σαν γενικός κανόνας, το build directory έχει νόημα μόνο για το μηχάνημα που κάνει compile. 2
2.2 Δομή microtcp πακέτων Η δομή κάθε microtcp πακέτου βασίζεται αρκετά στην δομή των TCP πακέτων με την διαϕορά ότι ο header κάθε microtcp πακέτου είναι πιο απλός και σταθερού μεγέθους. Ο header έχει την εξής μορϕή : Bit-0 Bit-16 Bit-31 Control Sequence Number ACK Number Data Length Future use field 0 Future use field 1 Future use field 2 CRC32 Checksum Window 1. Sequence Number, 32bit: Το πεδίο αυτό περιέχει το sequence number του πακέτου. Αρχικά κατά την έναρξη μιας microtcp σύνδεσης επιλέγεται ένας τυχαίος αριθμός. Μετά από κάθε επιτυχημένη μετάδοση, ο αριθμός αυτός αυξάνεται κατά τον αριθμό των bytes που στάλθηκαν επιτυχημένα. 2. Acknowledgment Number, 32bit: Το πεδίο αυτό περιέχει το sequence number που περιμένει ο αποστολέας να λάβει στο επόμενο πακέτο. 3. Control, 16bit: Σε αυτό το πεδίο περιέχεται η πληροϕορία για τον τύπο του κάθε πακέτου καθώς και κάποια flags που θα χρησιμοποιηθούν κυρίως στην επόμενη ϕάση. Bit 0 Bit-12 Bit-13 Bit 14 Bit-15 0 ACK RST SYN FIN ACK: Αν είναι 1, το Acknowledgment Number θα πρέπει να ληϕθεί υπόψιν. Στην ουσία μόνο το πρώτο πακέτο μιας TCP σύνδεσης δεν κάνει 1 αυτό το bit. Μετά το πρώτο SYN πακέτο, όλα τα υπόλοιπα θα πρέπει να έχουν το ACK bit 1. RST: Αν είναι 1, η σύνδεση θα πρέπει να γίνει reset. SYN: Synchronize sequence numbers. Χρησιμοποιείται για να δηλώσει την έναρξη μιας νέας σύνδεσης. Θα πρέπει να σταλθεί 1, μόνο μια ϕορά 3
από κάθε πλευρά κατά το πρώτο πακέτο. Στις υπόλοιπες περιπτώσεις θα πρέπει να είναι 0. FIN: Αν είναι 1, δηλώνει πως ο αποστολέας δεν έχει άλλα δεδομένα να στείλει. Χρησιμοποιείται για τον τερματισμό μιας σύνδεσης. 4. Window 16bit: Το window ορίζει τον αριθμό των bytes που είναι πρόθυμος να λάβει ο αποστολέας αυτού του πακέτου. 5. Data Length, 32bit: Το μέγεθος των δεδομένων του τρέχοντος πακέτου σε bytes. Προσοχή σε αυτόν τον αριθμό δεν θα πρέπει να προσμετρούνται τα bytes του header. 6. Future use 0, 32bit: Θα χρησιμοποιηθεί στην επόμενη ϕάση. Σε αυτή τη ϕάση θα πρέπει να είναι 0. 7. Future use 1, 32bit: Θα χρησιμοποιηθεί στην επόμενη ϕάση. Σε αυτή τη ϕάση θα πρέπει να είναι 0. 8. Future use 2, 32bit: Θα χρησιμοποιηθεί στην επόμενη ϕάση. Σε αυτή τη ϕάση θα πρέπει να είναι 0. 9. CRC32 Checksum 32bit: Περιέχει το CRC32 checksum του header και των δεδομένων του κάθε πακέτου. Για τον υπολογισμό του CRC32, υπάρχει υλοποιημένη η συνάρτηση crc32(const uint8_t *buf, size_t len) στο αρχείο utils/crc32.h. Ο υπολογισμός του checksum γίνεται ως εξής : Όλα τα πεδία του header συμπληρώνονται με την κατάλληλη πληροϕορία και τα δεδομένα τοποθετούνται ακριβώς μετά τον header. Η μνήμη των πεδίων που δεν χρησιμοποιούνται καθώς και τα 32-bits του CRC32 checksum πεδίου θα πρέπει να είναι 0. Η συνάρτηση crc32() εϕαρμόζεται από το πρώτο byte του πακέτου μέχρι το τελευταίο συμπεριλαμβανομένων των data bytes. Τα 32-bits που επιστρέϕει η συνάρτηση τοποθετούνται κατάλληλα στο αντίστοιχο πεδίο. Κατά το receive, ο receiver εξάγει το checksum και το αποθηκεύει κατάλληλα 4
τοπικά. Στο checksum πεδίο τοποθετεί μηδενικά bits και εϕαρμόζει την ίδια crc32() συνάρτηση όπως ακριβώς έκανε ο sender. Αν το checksum πεδίο που λήϕθηκε είναι ίδιο με αυτό που υπολογίστηκε από την συνάρτηση crc32(), το πακέτο περιέχει σωστά δεδομένα και μπορεί να συνεχιστεί η επεξεργασία του. Διαϕορετικά το πακέτο θεωρείται κατεστραμμένο. 3. Ζητούμενα Α Φάσης Στο αρχείο lib/microtcp.c θα βρείτε μια σειρά από συναρτήσεις που θα πρέπει να υλοποιήσετε. Η ονοματολογία τους σχετίζεται αρκετά με τις αντίστοιχες συναρτήσεις του TCP και η λειτουργικότητά τους θα πρέπει να είναι αντίστοιχη. microtcp_sock_t microtcp_socket(int domain, int type, int protocol); Η συνάρτηση αυτή δημιουργεί και επιστρέϕει ένα socket microtcp socket σε αντιστοιχία με την συνάρτηση socket(). Με την δημιουργία του socket το state της microtcp σύνδεσης θα πρέπει να ορίζεται ως UKNOWN. Σε περίπτωση λάθους κατάλληλο πεδίο του microtcp_sock_t θα πρέπει να ορίζεται κατάλληλα. int microtcp_bind( microtcp_sock_t *socket, const struct sockaddr *address, socklen_t address_len); Η συνάρτηση αυτή θα πρέπει να υλοποιεί ακριβώς την ίδια λειτουργικότητα με την bind(). int microtcp_accept( microtcp_sock_t *socket, struct sockaddr *address, socklen_t address_len); Η συνάρτηση αυτή μπλοκάρει έως ότου ένας client συνδεθεί στο microtcp socket που έχει ανοίξει ο server. Σε περίπτωση επιτυχίας, η microtcp_accept() επιστρέϕει 0 θέτοντας το πεδίο state του socket την τιμή ESTABLISHED. Σε περίπτωση λάθους επιστρέϕει -1 θέτοντας στο πεδίο state του socket την τιμή INVALID. Μετά την επιτυχημένη κλήση της συνάρτησης αυτής, στην επόμενη ϕάση ο client και ο server μπορούν να ανταλλάξουν δεδομένα. int microtcp_connect( microtcp_sock_t *socket, const struct sockaddr *address, socklen_t address_len); 5
Η microtcp_connect() συνδέεται σε ένα microtcp socket το οποίο περιμένει για συνδέσεις έχοντας καλέσει την microtcp_accept(). Η εγκαθίδρυση της σύνδεσης επιτυγχάνεται χρησιμοποιώντας την τεχνική 3-way handshake που περιγράϕεται παρακάτω. Μια επιτυχημένη κλήση της microtcp_connect() επιστρέϕει 0, θέτοντας στο πεδίο state του socket που πήρε ως παράμετρο, την τιμή ESTABLISHED. Σε περίπτωση λάθους, επιστρέϕει -1 και θέτει το state πεδίο του socket σε INVALID. ssize_t microtcp_send ( microtcp_sock_t *socket, const void *buffer, size_t length, int flags); Η συνάρτηση αυτή στέλνει τα δεδομένα του buffer χρησιμοποιώντας UDP. Ο αριθμός των bytes που πρέπει να σταλθούν καθορίζεται από την παράμετρο length. Για αυτή την ϕάση η λειτουργικότητά της είναι ιδιαίτερα απλή. Στέλνει τα δεδομένα με UDP χρησιμοποιώντας την sendto() συνάρτηση αναθέτοντας σωστά sequence numbers στο κατάλληλο πεδίο του πακέτου. Παράλληλα ανανεώνει και τα στατιστικά της σύνδεσης (packets sent, bytes sent). Η microtcp_send() επιστρέϕει των αριθμό των bytes που στάλθηκαν. ssize_t microtcp_recv ( microtcp_sock_t *socket, void *buffer, size_t length, int flags) Η συνάρτηση αυτή είναι υπεύθυνη για την λήψη δεδομένων από το δίκτυο. Τα δεδομένα αποθηκεύονται στην μνήμη που δείχνει η παράμετρος buffer, ενώ ο μέγιστος αριθμός bytes που μπορεί να ληϕθούν καθορίζεται από την παράμετρο length. Η συνάρτηση αυτή δεν πραγματοποιεί κάποια δυναμική δέσμευση μνήμης. Είναι ευθύνη του caller να δεσμεύσει αρκετή μνήμη και να την αναθέσει κατάλληλα στον buffer pointer. Σε αυτή την ϕάση η συνάρτηση αυτή απλά θα λαμβάνει τα δεδομένα και θα ελέγχει αν τυχόν χάθηκαν δεδομένα, χρησιμοποιώντας κατάλληλα την πληροϕορία του sequence number που υπάρχει σε κάθε πακέτο που λαμβάνει. Επιπλέον ανανεώνει κατάλληλα τις μεταβλητές των στατιστικών της σύνδεσης (packet received, packets lost, inter-arrivals κτλ). int microtcp_shutdown( microtcp_sock_t *socket, int how) Η συνάρτηση αυτή τερματίζει την σύνδεση μεταξύ server και client. Σε περίπτωση επιτυχίας επιστρέϕει 0, ενώ αντίθετα σε περίπτωση αποτυχίας -1. Και στις δύο περιπτώσεις, τροποποιεί κατάλληλα το πεδίο state του socket. Η τεχνική για τον τερματισμό της σύνδεσης περιγράϕεται παρακάτω αναλυτικά. Η παράμετρος how έχει την ίδια σημασία με αυτή που περιγράϕεται στην POSIX shutdown() και για ευκολία μπορεί να αγνοηθεί. 6
Επιπλέον, με τον τερματισμό της σύνδεσης θα πρέπει να εκτυπώνονται τα στατιστικά της με την εξής μορϕή : Packets received : xxxx Packets sent : xxxx Packets lost : xxxx Packet loss ratio : xxxx % Packet inter - arrival RX Min Max Mean Std : xxxx Packet inter - arrival TX Min Max Mean Std : xxxx 3.1 3-Way handshake s = sizeof(microtcp_header_t) microtcp_connect() SYN, seq=n microtcp_accpept() SYN, ACK, seq=m, ack=n+s ACK, seq=n+s, ack=m+s Με το 3-way handshake γίνεται η εγκαθίδρυση της σύνδεσης. Για να γίνει αυτό ο client και ο server πρέπει να ανταλλάξουν πακέτα με τα κατάλληλα sequence numbers. 7
Το πρώτο πακέτο που στέλνει ο client είναι το SYN (Synchronize) πακέτο, το οποίο περιέχει το sequence number Ν που διάλεξε τυχαία. Όπως περιγράϕτηκε και προηγουμένως, το SYN πακέτο δηλώνεται θέτοντας σε 1 το κατάλληλο bit στο control πεδίου του header. Ο server λαμβάνει το SYN πακέτο και αποθηκεύει το sequence number του client. Έπειτα διαλέγει τυχαία ένα δικό του sequence number Μ και στέλνει ένα πακέτο SYN+ACK πίσω στον client, το οποίο περιέχει στο πεδίο Sequence Number τον αριθμό M και στο πεδίο ACK Number τον αριθμό N+s. To SYN+ACK πακέτο δηλώνεται θέτοντας σε 1 τα αντίστοιχα bits στο control πεδίου του header. O client λαμβάνει το SYN+ACK πακέτο, αποθηκεύει το sequence number M του server και στέλνει ένα ACK με ACK number Μ+s. Με το τέλος της διαδικασίας η σύνδεση θεωρείται εγκαθιδρυμένη. Το s αντιστοιχεί στον αριθμό των bytes που στάλθηκαν. Κατά το 3-way handshake το κάθε microtcp πακέτο περιέχει μόνο τους headers του, συνεπώς το s θα είναι ίσο με sizeof(microtcp_header_t). Σημείωση : Κατά την διάρκεια της χειραψίας, υπάρχει πάντα η περίπτωση κάποιο πακέτο να χαθεί ή να περιέχει λάθος δεδομένα. Ο έλεγχος τέτοιων περιπτώσεων θα γίνει στην επόμενη ϕάση. 3.2 Connection termination microtcp_shutdown() FIN, ACK, seq=x ACK, ack=x+s FIN, ACK, seq=y ACK, seq=x+s, ack=y+s Για τον τερματισμό μιας microtcp σύνδεσης ακολουθείται μια παρόμοια διαδικασία 8
με το 3-way handshake. Έστω S o κόμβος που θέλει να τερματήσει την σύνδεση και D ο άλλος συμμετέχων την σύνδεσης. Ο S στέλνει ένα FIN πακέτο, δηλώνοντας στον D πως επιθυμεί την διακοπή της σύνδεσης. Ο D απαντάει στο FIN πακέτο με ένα ACK και θέτει την σύνδεση σε state CLOSING_BY_PEER. Μόλις ο S λάβει το ACK, θέτει την σύνδεση σε state CLOSING_BY_HOST. O D εκτελεί όποιες λειτουργίες έχουν απομείνει και στέλνει ένα FIN πακέτο στον S. Μόλις λάβει το FIN πακέτο ο S, απαντάει στον D με ένα ACK και θέτει το state της σύνδεσής του σε CLOSED. Όταν αντίστοιχα λάβει ο D το ACK, θέτει το state της σύνδεσής του και αυτός σε CLOSED. Σημείωση : Σε μια microtcp σύνδεση, όπως και στο κανονικό TCP, οποιοσδήποτε από τους δύο peers της σύνδεσης μπορεί να ξεκινήσει το shutdown. 3.3 Στατιστικά σύνδεσης Ο κάθε peer της σύνδεσης για κάθε πακέτο που λαμβάνει ή στέλνει, και για όλη την διάρκεια της σύνδεσης, ανανεώνει τα στατιστικά της, τροποποιώντας τα κατάλληλα πεδία της δομής microtcp_sock_t. Πιο συγκεκριμένα, τα πεδία αυτά είναι : packets_sent: Ο αριθμός των πακέτων που έχει σταλθεί packets_received: Ο αριθμός των πακέτων που έχει ληϕθεί packets_lost: Ο αριθμός των πακέτων που χάθηκαν κατά την λήψη rx_min_inter: O μικρότερος χρόνος μεταξύ δύο αϕίξεων πακέτων rx_max_inter: O μεγαλύτερος χρόνος μεταξύ δύο αϕίξεων πακέτων rx_mean_inter: O μέσος χρόνος μεταξύ δύο αϕίξεων πακέτων tx_min_inter: O μικρότερος χρόνος μεταξύ δύο αποστολών πακέτων tx_max_inter: O μεγαλύτερος χρόνος μεταξύ δύο αποστολών πακέτων tx_mean_inter: O μέσος χρόνος μεταξύ δύο αποστολών πακέτων 3.4 Ανάλυση στατιστικών σύνδεσης Στον ϕάκελο test θα βρείτε το εργαλείο traffic_generator το οποίο παράγει πακέτα με ένα inter-arrival που ακολουθεί poisson κατανομή. Η μέση τιμή της κατανομής σε milliseconds δίνεται σαν παράμετρος κατα την εκτέλεση, με το όρισμα -i. Το traffic_generator λειτουργεί ως server, οπότε χρειάζεται και μία port στην οποία θα περιμένει κάποιον client. Την port μπορείτε να την ορίσετε με την παράμετρο -p. 9
Για παράδειγμα, η εκτέλεση traffic_generator -p 15800 -i 100 θα δημιουργήσει ένα micro_tcp server που τρέχει στην port 15800 και περιμένει έναν client να συνδεθεί. Μόλις ο client συνδεθεί, θα αρχίσει να στέλνει πακέτα προς τον client, των οποίων το inter-arrival ακολουθεί Poisson κατανομή με μέση τιμή 100 ms. Εσείς θα πρέπει να υλοποιήσετε τον client στο αρχείο traffic_generator_client.c που υπάρχει στον ϕάκελο test. Εκεί Θα πρέπει να καταγράψετε τους inter-arrival χρόνους των πακέτων. Μετά χρησιμοποιώντας οποιοδήποτε εργαλείο της αρεσκείας σας (πχ Octave, GNUplot, matplotlib, Matlab) κάντε plot το ιστόγραμμα και την CDF των inter-arrivals. Θα πρέπει τα γραϕήματά σας να δείχνουν μια κατανομή που να έχει τα χαρακτηριστικά της κίνησης που στέλνει ο traffic generator (είδος κατανομής, μέση τιμή). Πιο συγκεκριμένα μαζί με τον κώδικα της πρώτης ϕάσης θα πρέπει να παραδώσετε και τα αντίστοιχα plots για τρεις διαϕορετικές μετρήσεις. Για την πρώτη τρέξτε το εργαλείο traffic_generator με μέση τιμή 100ms, για την δεύτερη 250ms, ενώ για την τρίτη 750ms. 3.5 Bandwidth Test Σαν μέρος του project είναι και η υλοποίηση ενός εργαλείου μέτρησης του bandwidth που επιτυγχάνεται μεταξύ ενός server και ενός client τόσο με την χρήση του microtcp όσο και με το κανονικό TCP. Στο αρχείο test/bandwidth_test.c θα πρέπει να υλοποιήσετε τις απαραίτητες συναρτήσεις που ζητούνται. Το πρόγραμμα παίρνει ως παράμετρο ένα όνομα αρχείου, το πρωτόκολλο μεταϕορά (TCP ή microtcp) καθώς και αν θα λειτουργήσει ως server ή client. Ο client θα πρέπει να στείλει σωστά το αρχείο στον server και στο τέλος να εκτυπώνει την διάρκεια της μεταϕοράς και το συνολικό bandwidth που επιτεύχθηκε. Όπως θα διαπιστώσετε, το μεγαλύτερο μέρος του εργαλείου σας δίνεται ήδη υλοποιημένο. Θα πρέπει να συμπληρώσετε κατάλληλα την συνάρτηση server_microtcp() και client_microtcp(). Μπορείτε να βασιστείτε στις υλοποιημένες συναρτήσεις server_tcp() και client_tcp() Σημείωση : Η πλήρης υλοποίηση του εργαλείου δεν είναι απαραίτητη για την πρώτη ϕάση, αλλά συνιστάται ιδιαίτερα να υλοποιήσετε το μεγαλύτερό του μέρος ώστε να είστε σε θέση να λάβετε feedback στην εξέταση της πρώτης ϕάσης. 10