#include #include /* TP BE Réseaux À L'ATTENTION DU CORRECTEUR : - Ce fichier est le seul fichier que j'ai modifié - La v1 a été implémenté et fonctionne avec tsock_texte et tsock_video. - La v2 a été implémentée et reprise de perte ok. Pour tester la v2, vous devez faire quelques modifications : * Régler les parametres comme suivant : MOD_SEQ_NUM = 2 ADMITTED_LOSS_RATE = 0 * Il faut décommenter les lignes commençant par // [V2] Ces lignes sont dans la fonction process_received_PDU(). - La v3 est implémentée et fonctionnelle. ADMITTED_LOSS_RATE permet de parametrer le taux de paquets perdus abandonnés. S'il vaut 5, alors 5% des paquets perdus ne seront pas renvoyés. Pour tester la v3, passez : * DO_HANDSHAKE = 0 Utiliser cette version permet d'avoir moins de ralentissements, mais au prix de coupure et des pertes d'informations. - La v4.1 est implémentée et fonctionnelle. Le handshake permet également d'échanger le taux de pertes admissibles. Pour tester la v3, le handshake peut être désactivé avec DO_HANDSHAKE = 0. On peut faire plusieurs remarques sur le programme final : - Le taux de pertes admis (ADMITTED_LOSS_RATE) est calculé à chaque itération à partir de l'ensemble des pertes et des envoies fait jusqu'à présent. On aurait pu implémenter une fenêtre (e.g. avec une pile FIFO, et ça serait plus pertinent pour de longues transmissions) mais il est alors plus difficile d'observer les effets de la v3 et de se s'arrurer que les taux de pertes admises sont corrects. - La gestion des pertes admissibles est entièrement gérée par la source et fonctionne sur le principe du stop and wait uniquement. Améliorations possibles : Je vous propose ici quelques idées pour aller plus loin que je n'ai pas eu le temps d'implémenter : - Mettre en place de l'asynchronisme entre le client et MIC TCP. Cela pourrait être fait avec un buffer créant une file d'attente avant le stop and wait. - Mettre en place le multiplexage, en passant toutes les variables globales dans un struct pour chaque socket et utiliser le numéro de port pour identifier à quel socket un paquet appartient. - Utiliser le RTT plutôt qu'un timeout fixe - Regarder l'implémentation de TCP pour le remplacer par MIC-TCP avec par exemple l'utilisation de LD_PRELOAD (bon ça, c'est vraiment ambitieux). */ /* RÉGLAGE DES FONCTIONNALITÉES */ // Taux de perte simulé (%) #define LOSS_RATE 10 // Taux de perte admis (%)(pour la v3) #define ADMITTED_LOSS_RATE 5 // Nombre de numéros de séquence différents possible (v3 : 2147483646) #define MOD_SEQ_NUM 2147483646 // 0: Ne pas utiliser les fonctions connect() et accept() // 1: Utiliser ces fonctions (facultatif dans le BE) #define DO_HANDSHAKE 1 // Niveau de verbositée: 0, faible; 1, fort #define DEBUG 1 // Autres parametres #define MAX_SOCK 10 #define TIMEOUT 2 #define N_S 0 /// Variable globale // Contient l'ensemble des sockets créés, dans les faits // seul l'utilisation d'un seul socket a été implémenté (n°0) mic_tcp_sock tab_sock[MAX_SOCK]; // Contient le loss rate connu int admitted_loss_rate = 0; // Numéros de séquence int seq_num = 0; int ack_num = 0; // Compteur permettant de savoir si des packets // ont été perdus. --> Aucune pertes pour la v2. int rcv_c = 0; /* Compteur de paquets reçus */ int lss_c = 0; /* Compteur de paquets perdus */ int snd_c = 0; /* Compteur de paquets envoyés */ // Compteur pour la v3 int acc_c = 0; /* Compteur des pertes renvoyés */ int iac_c = 1; /* Compteur des pertes abandonnée */ /* * Fonction initialisant toutes les composantes d'un PDU * à zéro. */ void reset_pdu(mic_tcp_pdu *pdu) { (*pdu).header.source_port = 0; (*pdu).header.dest_port = 0; (*pdu).header.seq_num = 0; (*pdu).header.ack_num = 0; (*pdu).header.syn = 0; (*pdu).header.ack = 0; (*pdu).header.fin = 0; (*pdu).payload.data = NULL; (*pdu).payload.size = 0; } /* * Permet de créer un socket entre l’application et MIC-TCP * Retourne le descripteur du socket ou bien -1 en cas d'erreur */ int mic_tcp_socket(start_mode sm) { if (DEBUG) { printf("[MIC-TCP] Appel de la fonction: "); printf(__FUNCTION__); printf("\n"); } int result = -1; mic_tcp_sock s; result = initialize_components(sm); /* Appel obligatoire */ s.fd = 0; if (result != -1) result = s.fd; set_loss_rate(LOSS_RATE); return result; } /* * Permet d’attribuer une adresse à un socket. * Retourne 0 si succès, et -1 en cas d’échec */ int mic_tcp_bind(int socket, mic_tcp_sock_addr addr) { if (DEBUG) { printf("[MIC-TCP] Appel de la fonction: "); printf(__FUNCTION__); printf("\n"); } // On test que l'identifiant donné est bon if (socket < 0 || socket >= MAX_SOCK) { return -1; } tab_sock[socket].state = IDLE; tab_sock[socket].addr = addr; return 0; } /* * Met le socket en état d'acceptation de connexions * Retourne 0 si succès, -1 si erreur */ // Fonction implélentée dans le cadre de la v4 int mic_tcp_accept(int socket, mic_tcp_sock_addr *addr) { if (DEBUG) { printf("[MIC-TCP] Appel de la fonction: "); printf(__FUNCTION__); printf("\n"); } int success = 0; if (!DO_HANDSHAKE) { admitted_loss_rate = ADMITTED_LOSS_RATE; goto skip; } mic_tcp_pdu pdu; // On attend un SYN, process_received_PDU() doit nous le donner while (tab_sock[socket].state != SYN_RECEIVED) {} printf("[CONNECTION STATE] SYN RCV\n"); // Construction du SYN ACK reset_pdu(&pdu); pdu.header.source_port = tab_sock[socket].addr.port; pdu.header.dest_port = addr->port; pdu.header.syn = 1; pdu.header.ack = 1; // On l'envoie if ((success = IP_send(pdu, *addr)) == -1) { return -1; } printf("[CONNECTION STATE] SYNACK sent.\n"); // On attend l'ACK while (tab_sock[socket].state != ESTABLISHED) { IP_send(pdu, *addr); printf("[CONNECTION STATE] SYNACK re-sent.\n"); } printf("[CONNECTION STATE] ESTABLISHED\n"); skip: tab_sock[socket].state = ESTABLISHED; return success; } /* * Permet de réclamer l’établissement d’une connexion * Retourne 0 si la connexion est établie, et -1 en cas d’échec */ // Fonction implélentée dans le cadre de la v4 int mic_tcp_connect(int socket, mic_tcp_sock_addr addr) { if (DEBUG) { printf("[MIC-TCP] Appel de la fonction: "); printf(__FUNCTION__); printf("\n"); } admitted_loss_rate = ADMITTED_LOSS_RATE; if (!DO_HANDSHAKE) goto skip; int success = -1; mic_tcp_pdu pdu, pdu_rcv; // On vérifie que notre socket est prêt if (tab_sock[socket].state != IDLE){ return -1; } // On construit notre pdu SYN et on l'envoie printf("[CONNECTION STATE] SYN\n"); reset_pdu(&pdu); pdu.header.dest_port = addr.port; pdu.header.syn = 1; pdu.payload.data = (char *) &admitted_loss_rate; pdu.payload.size = 4; printf("[CONNECTION STATE] Config done. Sending..."); if ((success = IP_send(pdu, addr)) == -1){ printf("Fail !\n"); return -1; } printf("Done.\n"); // On attend le SYN ACK while (1){ printf("[CONNECTION STATE] Wait SYNACK (d.: %d)\n", success); reset_pdu(&pdu_rcv); success = IP_recv(&pdu_rcv, &addr, TIMEOUT); if (pdu_rcv.header.syn == 1 && pdu_rcv.header.ack == 1){ printf("[CONNECTION STATE] SYNACK, sending ACK.\n"); pdu.header.syn = 0; pdu.header.ack = 1; if ((success = IP_send(pdu, addr)) == -1){ return -1; } break; } IP_send(pdu, addr); } // Connecté ! skip: tab_sock[socket].state = ESTABLISHED; tab_sock[socket].addr = addr; if (DEBUG) printf("[CONNECTION STATE] ESTABLISHED\n"); return 0; } /* * Fonction determinant si une perte est acceptable ou non. * Elle effectue des calculs sur integer uniquement pour * augmenter sa réactivité. */ int is_acceptable(){ return acc_c * 1000000 / (10000 * (iac_c+acc_c)) < admitted_loss_rate && ADMITTED_LOSS_RATE; } /* * Permet de réclamer l’envoi d’une donnée applicative * Retourne la taille des données envoyées, et -1 en cas d'erreur */ int mic_tcp_send(int mic_sock, char *mesg, int mesg_size) { if (DEBUG) { printf("[MIC-TCP] Appel de la fonction: "); printf(__FUNCTION__); printf("\n"); } if (tab_sock[mic_sock].state != ESTABLISHED) return -1; mic_tcp_pdu pdu, rcv; int sq = seq_num; int sent_size = -1; seq_num = (seq_num + 1) % MOD_SEQ_NUM; reset_pdu(&pdu); pdu.header.dest_port = tab_sock[mic_sock].addr.port; pdu.header.seq_num = sq; pdu.payload.data = mesg; pdu.payload.size = mesg_size; int ok = 0; int counted = 0; while (!ok) { if (DEBUG) printf("[DEBUG: mic_tcp_send] Envoi du paquet N°%d...\n", sq); sent_size = IP_send(pdu, tab_sock[mic_sock].addr); if (sent_size <= 0){ printf("[ERREUR] Erreur send().\n"); return -1; } if (DEBUG) printf("[DEBUG: mic_tcp_send] Attente de l'ACK n°%d...\n", sq); reset_pdu(&rcv); IP_recv(&rcv, &(tab_sock[mic_sock].addr), TIMEOUT); // Doit-on ne renvoyer pas le paquet ? if (rcv.header.ack_num == sq && rcv.header.ack == 1) { // Envoi ok. ok++; snd_c++; if (DEBUG) printf("[DEBUG V3] Envoi ok : n°%d (%d/%d)\n", sq, acc_c, iac_c); } // Conditions pour la v3 uniquement. else if (is_acceptable()) { // Envoi nok, mais acceptable ok++; lss_c++; acc_c++; if (DEBUG) printf("[DEBUG V3] Perte acceptable : n°%d (%d/%d)\n", sq, acc_c, iac_c); } else { // Envoi nok, inacceptable if (!counted) { iac_c++; counted++; } if (DEBUG) printf("[DEBUG V3] Perte inacceptable : n°%d (%d/%d)\n", sq, acc_c, iac_c); } } if (DEBUG) printf("[DEBUG] ACK n°%d reçu. Nb de pckt send : %d\n", rcv.header.ack_num, snd_c); return sent_size; } /* * Permet à l’application réceptrice de réclamer la récupération d’une donnée * stockée dans les buffers de réception du socket * Retourne le nombre d’octets lu ou bien -1 en cas d’erreur * NB : cette fonction fait appel à la fonction app_buffer_get() */ int mic_tcp_recv(int socket, char *mesg, int max_mesg_size) { if (DEBUG) { printf("[MIC-TCP] Appel de la fonction: "); printf(__FUNCTION__); printf("\n"); } if (tab_sock[socket].state != ESTABLISHED) return -1; mic_tcp_payload payload; payload.data = mesg; payload.size = max_mesg_size; int read_size = app_buffer_get(payload); if (DEBUG) printf("[DEBUG] Read: read_size = %d\n", read_size); if (read_size > 0) rcv_c++; if (DEBUG) printf("[DEBUG] Nb de pckt recu : %d\n", rcv_c); return read_size; } /* * Permet de réclamer la destruction d’un socket. * Engendre la fermeture de la connexion suivant le modèle de TCP. * Retourne 0 si tout se passe bien et -1 en cas d'erreur */ // Non implémentée cette année, retourne 0 dans tous les cas int mic_tcp_close(int socket) { if (DEBUG) { printf("[MIC-TCP] Appel de la fonction : "); printf(__FUNCTION__); printf("\n"); } tab_sock[socket].state = CLOSED; return 0; } /* * Traitement d’un PDU MIC-TCP reçu (mise à jour des numéros de séquence * et d'acquittement, etc.) puis insère les données utiles du PDU dans * le buffer de réception du socket. Cette fonction utilise la fonction * app_buffer_put(). */ void process_received_PDU(mic_tcp_pdu pdu, mic_tcp_sock_addr addr) { if (DEBUG) { printf("[MIC-TCP] Appel de la fonction: "); printf(__FUNCTION__); printf("\n"); } switch (tab_sock[N_S].state) { case IDLE: if (pdu.header.syn == 1) { admitted_loss_rate = *((int*)pdu.payload.data); if (DEBUG) printf("[CONNECTION STATE] Getting loss rate : %d\n", admitted_loss_rate); tab_sock[N_S].state = SYN_RECEIVED; } else { if (DEBUG) printf("[DEBUG] Paquet ignoré.\n"); } break; case SYN_RECEIVED: if (pdu.header.ack == 1) { tab_sock[N_S].state = ESTABLISHED; } else { if (DEBUG) printf("[DEBUG] Paquet ignoré.\n"); } break; case CLOSED: if (DEBUG) printf("[DEBUG] Reception d'un paquet sur un port fermé\n"); break; case ESTABLISHED: if (DEBUG) printf("[DEBUG: ESTABLISHED] Rcv n°%d, wait n° %d\n", pdu.header.seq_num, ack_num); // [V2] if (pdu.header.seq_num == ack_num) if (pdu.header.seq_num >= ack_num) { if (DEBUG) printf("[DEBUG] Num ok. In buffer.\n"); app_buffer_put(pdu.payload); // [V2] ack_num = (ack_num + 1) % MOD_SEQ_NUM; ack_num = pdu.header.seq_num + 1; // Préparation du ACK mic_tcp_pdu ack; reset_pdu(&ack); int s; ack.header.source_port = pdu.header.source_port; ack.header.dest_port = addr.port; ack.header.ack_num = pdu.header.seq_num; ack.header.ack = 1; if (DEBUG) printf("[DEBUG] Envoi du ACK\n"); if ((s = IP_send(ack, addr)) == -1) { printf("[ERREUR] Envoi ACK fail.\n"); return; } } else { if (DEBUG) printf("[DEBUG] No go.\n"); mic_tcp_pdu ack; reset_pdu(&ack); int s; ack.header.source_port = pdu.header.source_port; ack.header.dest_port = addr.port; ack.header.ack = 1; ack.header.ack_num = pdu.header.seq_num; if (DEBUG) printf("[DEBUG] Envoi du ACK N°%d\n", ack.header.ack_num); if ((s = IP_send(ack, addr)) == -1) { printf("[ERREUR] Envoi ACK fail.\n"); return; } if (DEBUG) printf("[DEBUG] Ignored.\n"); } break; default: break; } }