Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
No results found
Show changes
Showing
with 1097 additions and 0 deletions
# Algorithmes des réseaux
## Expéditeur TCP IPv4
Complétez le programme `sender-tcp.c` pour envoyer le message `hello world` avec les protocoles `IPv4` et `TCP`.
Le programme admet en argument le numéro de port de l'hôte distant à contacter :
./sender-tcp port_number
Les seuls numéros de port valides sont ceux contenus dans l'intervalle [10000; 65000].
**Objectifs :** savoir créer un socket et transmettre un message texte en TCP.
## Marche à suivre
Vous devez dans un premier temps créer un socket `IPv4` et `TCP` via la primitive :
int socket (int domain, int type, int protocol)
Ensuite, vous pouvez récupérer votre code de l'exercice 2 pour renseigner l'adresse `IPv4` et le port du destinataire à l'aide de la fonction `getaddrinfo()`. Le destinataire sera joignable sur l'adresse `127.0.0.1` et le port passé en argument du programme. N'oubliez pas de mettre à jour le type de socket dans la structure `hints`.
Vous devez maintenant vous connecter au serveur pour initier la transaction TCP (3-ways handshake) via la fonction :
int connect (int socket, const struct sockaddr *address, socklen_t address_len)
Enfin, l'envoi du message se fait via la primitive :
ssize_t send (int socket, const void *buffer, size_t length, int flags)
Sans option (`flags = 0`), cette primitive est équivalente à :
ssize_t write (int fildes, const void *buf, size_t nbyte)
Vous pouvez tester votre programme en exécutant la commande `nc` (netcat) dans un autre terminal afin d'écouter en TCP sur l'adresse `127.0.0.1` et le port de votre choix :
nc -4l 127.0.0.1 port_number
Vous pouvez ensuite lancer votre programme dans un autre terminal avec le même port en argument et vérifier que le message transmis est bien affiché par `netcat` sur la sortie standard.
## Validation
Votre programme doit obligatoirement passer tous les tests sur gitlab (il suffit de `commit/push` le fichier source pour déclencher le pipeline de compilation et de tests) avant de passer à l'exercice suivant.
sources = Glob ("*.c")
CFLAGS = ["-Wall", "-Wextra", "-Werror", "-g"]
env = Environment (CCFLAGS = CFLAGS)
env.Program (sources)
#include <sys/socket.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netdb.h>
#define CHECK(op) do { if ( (op) == -1) { perror (#op); exit (EXIT_FAILURE); } \
} while (0)
#define IP "127.0.0.1"
#define SIZE 100
void raler(const char *chaine) {
fprintf(stderr, "%s\n", chaine);
exit(EXIT_FAILURE);
}
int main (int argc, char *argv [])
{
char buffer[SIZE]="hello world";
/* test arg number */
if(argc != 3){
fprintf(stderr, "usage: %s ip_addr port_number\n", argv[0]);
exit(EXIT_FAILURE);
}
/* convert and check port number */
int port_number =atoi(argv[2]);
if(port_number<10000||port_number>65000){
fprintf(stderr, "usage: %d port_number is not in [10000;65000] \n", port_number);
exit(EXIT_FAILURE);
}
/* create socket */
int new_socket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
CHECK(new_socket);
struct addrinfo hints, *res;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET; // Use IPv4
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
int error = getaddrinfo(argv[1], argv[2], &hints, &res);
if (error != 0) {
fprintf(stderr,"getaddrinfo %s\n",gai_strerror(error)); // Generic error message
exit(1); // Set the exit status to 1
}
/* connect to the remote peer */
CHECK(connect(new_socket, res->ai_addr, res->ai_addrlen));
/* send the message */
CHECK(send(new_socket, buffer, 11, 0));
close(new_socket);
/* free memory */
freeaddrinfo(res);
return 0;
}
#!/bin/bash
PROG="./sender-tcp"
FILE="$PROG.c"
PORT=`shuf -i 10000-65000 -n 1`
OUT="/tmp/$$"
mkdir $OUT
IP="127.0.0.1"
SE=$(uname -s)
######################################
echo -n "test 01 - program usage: "
echo "usage: $PROG ip_addr port_number" > $OUT/usage
$PROG > $OUT/stdout 2> $OUT/stderr && echo "KO -> exit status $? instead of 1" && exit 1
! cmp -s $OUT/usage $OUT/stderr && echo "KO -> unexpected output on stderr => check file \"$OUT/usage\"" && exit 1
[ -s $OUT/stdout ] && echo "KO -> output detected on stdout" && exit 1
$PROG a > $OUT/stdout 2> $OUT/stderr && echo "KO -> exit status $? instead of 1" && exit 1
! cmp -s $OUT/usage $OUT/stderr && echo "KO -> unexpected output on stderr => check file \"$OUT/usage\"" && exit 1
[ -s $OUT/stdout ] && echo "KO -> output detected on stdout" && exit 1
$PROG a b c > $OUT/stdout 2> $OUT/stderr && echo "KO -> exit status $? instead of 1" && exit 1
! cmp -s $OUT/usage $OUT/stderr && echo "KO -> unexpected output on stderr => check file \"$OUT/usage\"" && exit 1
[ -s $OUT/stdout ] && echo "KO -> output detected on stdout" && exit 1
echo ".....................OK"
######################################
echo -n "test 02 - no dynamic memory allocation: "
grep -q "[cm]alloc" $FILE && echo "KO -> dynamic memory allocation is not allowed!" && exit 1
echo "......OK"
######################################
echo -n "test 03 - invalid port number: "
$PROG a 65001 > $OUT/stdout 2> $OUT/stderr && echo "KO -> exit status $? instead of 1" && exit 1
! [ -s $OUT/stderr ] && echo "KO -> no output on stderr" && exit 1
[ -s $OUT/stdout ] && echo "KO -> output detected on stdout" && exit 1
$PROG a 9999 > $OUT/stdout 2> $OUT/stderr && echo "KO -> exit status $? instead of 1" && exit 1
! [ -s $OUT/stderr ] && echo "KO -> no output on stderr" && exit 1
[ -s $OUT/stdout ] && echo "KO -> output detected on stdout" && exit 1
echo "...............OK"
######################################
echo -n "test 04 - getaddrinfo usage: "
ERROR="Name or service not known"
if [ "$SE" == "Darwin" ]; then
ERROR="nodename nor servname provided, or not known"
fi
LC_ALL=C $PROG a $PORT > $OUT/stdout 2> $OUT/stderr && echo "KO -> exit status $? instead of 1" && exit 1
! grep -q "$ERROR" $OUT/stderr && echo "KO -> unexpected output on stderr, you should use gai_strerror" && exit 1
grep -q "$ERROR" $FILE && echo "KO -> no static msg for getaddrinfo error" && exit 1
[ -s $OUT/stdout ] && echo "KO -> output detected on stdout" && exit 1
echo ".................OK"
######################################
echo -n "test 05 - check connect error: "
LC_ALL=C $PROG $IP 23451 > $OUT/stdout 2> $OUT/stderr && echo "KO -> exit status $? instead of 1" && exit 1
ERROR="Connection refused"
! grep -q "$ERROR" $OUT/stderr && echo "KO -> unexpected output on stderr, do you use perror?" && exit 1
[ -s $OUT/stdout ] && echo "KO -> output detected on stdout" && exit 1
echo "...............OK"
######################################
echo -n "test 06 - use struct returned by getaddrinfo: "
grep -q "sockaddr_.*" $FILE && echo "KO -> you should not use struct sockaddrX variable" && exit 1
echo "OK"
######################################
echo -n "test 07 - program exits without error: "
timeout 10 nc -4l $IP $PORT > $OUT/msg_r &
sleep 4
! $PROG $IP $PORT > $OUT/stdin 2> $OUT/stderr && echo "KO -> exit status != 0" && exit 1
[ -s $OUT/stderr ] && echo "KO -> output detected on stderr" && exit 1
[ -s $OUT/stdout ] && echo "KO -> output detected on stdout" && exit 1
echo ".......OK"
######################################
echo -n "test 08 - program sends a message: "
! [ -s $OUT/msg_r ] && echo "KO -> no message transmitted" && exit 1
echo "...........OK"
######################################
echo -n "test 09 - message is valid: "
printf "hello world" > $OUT/msg_o
! cmp -s $OUT/msg_o $OUT/msg_r && echo "KO -> check files \"$OUT/msg_o\" (expected) and \"$OUT/msg_r\" (sent)" && exit 1
echo "..................OK"
######################################
echo -n "test 10 - memory error: "
P=`which valgrind`
[ -z "$P" ] && echo "KO -> please install valgrind" && exit 1
timeout 10 nc -4l $IP $PORT > /dev/null &
sleep 4
valgrind --leak-check=full --error-exitcode=100 --log-file=$OUT/valgrind.log $PROG $IP $PORT
[ "$?" == "100" ] && echo "KO -> memory pb please check valgrind.log" && exit 1
echo "..................................OK"
rm -r $OUT
stages:
- build
- test
image: montavont/algodesreseaux
build_08:
stage: build
script:
- cd 08-recepteurTCPv4
- scons
artifacts:
paths:
- 08-recepteurTCPv4/receiver-tcp
# run tests using the binary build before
test_08:
stage: test
needs: [build_08]
script:
- |
echo "starting tests"
cd 08-recepteurTCPv4
bash tests.sh
File added
# Algorithmes des réseaux
## Récepteur TCP IPv4
Complétez le programme `receiver-tcp.c` pour recevoir un message texte avec les protocoles `IPv4` et `TCP` et l'afficher sur la sortie standard. Vous devrez également afficher l'adresse IP et le port du client dès sa connexion à votre programme.
Le programme admet en argument l'adresse IP et le numéro de port sur lequel le programme doit écouter :
./receiver-tcp ip_addr port_number
Les seuls numéros de port valides sont ceux contenus dans l'intervalle `[10000; 65000]`.
La sortie de votre programme doit être de la forme suivante :
./receiver-tcp 192.168.1.233 12345
192.168.1.1 54678
hello world
Une telle sortie indique que le client ayant pour adresse `192.168.1.1` et le port `54678` s'est connecté à votre programme et a transmis le message `hello world`.
Votre programme devra être en mesure de recevoir jusqu'à SIZE octets.
**Objectifs :** savoir créer un socket, se mettre en attente d'une connexion TCP entrante, et réceptionner un message texte en TCP.
## Marche à suivre
Vous devez dans un premier temps créer un socket `IPv4` et `TCP` via la primitive :
int socket (int domain, int type, int protocol)
Le protocole `TCP` met un temps non négligeable pour fermer un socket afin d'être sûr que tous les segments appartenant à la connexion qui vient de se terminer ne puissent pas être acceptés à tort par une nouvelle connexion avec le même quadruplet (adresse source, port source, adresse destination, port destination). Cela permet également de s'assurer que l'hôte distant a bien terminé la connexion. Cependant, ce mécanisme ralentit significativement les tests qui utilisent le même quadruplet à plusieurs reprises. Pour éviter cette attente, vous devez ajouter le code suivant après avoir créé le socket (en supposant que le descripteur correspondant est contenu dans la variable `sockfd`):
/* SO_REUSEADDR option allows re-starting the program without delay */
int iSetOption = 1;
CHECK (setsockopt (sockfd, SOL_SOCKET, SO_REUSEADDR, &iSetOption, sizeof iSetOption));
Ensuite, vous pouvez récupérer votre code de l'exercice 4 pour renseigner l'adresse IP et le port passés en argument du programme à l'aide de la fonction `getaddrinfo()` et lier le socket avec `bind()`. N'oubliez pas de mettre à jour le type de socket dans la structure `hints`.
Il faut maintenant configurer la taille de la file d'attente pour les connexions entrantes via la primitive :
int listen (int socket, int backlog)
Ensuite il faut attendre les demandes de connexion (réception de messages `SYN`) via la primitive :
int accept (int socket, struct sockaddr *restrict address, socklen_t *restrict address_len)
Vous pouvez récupérer des informations sur le client qui vient de se connecter via les deux derniers paramètres de la primitive `accept()` qui permettent de compléter une variable de type `struct sockaddr_storage` castée en une structure `struct sockaddr`.
Ensuite, vous pouvez transformer les informations contenues dans cette structure (adresse IP et port) en des chaînes de caractères via la fonction `getnameinfo()` comme réalisé dans l'exercie 4.
Enfin, la réception d'un message sur le nouveau socket retourné par `accept()` se fait via la primitive :
ssize_t recv (int socket, void *buffer, size_t length, int flags)
Sans option (`flags = 0`) cette primitive est équivalente à :
ssize_t read (int desc, void *buf, size_t nbyte)
que vous pouvez utiliser si vous êtes nostalgique de l'UE programmation système.
Vous pouvez tester votre programme avec le programme réalisé dans l'exercice précédent ou en exécutant la commande `nc` (netcat) dans un autre terminal. Lancez votre programme dans un terminal puis exécutez la commande suivante dans un second terminal afin d'envoyer le message `hello world` en `TCP` sur l'adresse IP et le port utilisé par votre programme, et en utilisant le port local de votre choix :
echo "hello world" | nc -4 -w1 ip_du_prog port_du_prog -p port_local_pour_netcat
Vérifiez ensuite que l'adresse et le port de l'expéditeur ainsi que le message sont correctement affichés par votre programme sur la sortie standard.
## Validation
Votre programme doit obligatoirement passer tous les tests sur gitlab (il suffit de `commit/push` le fichier source pour déclencher le pipeline de compilation et de tests) avant de passer à l'exercice suivant.
sources = Glob ("*.c")
CFLAGS = ["-Wall", "-Wextra", "-Werror", "-g"]
env = Environment (CCFLAGS = CFLAGS)
env.Program (sources)
#include <sys/socket.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netdb.h>
#define CHECK(op) do { if ( (op) == -1) { perror (#op); exit (EXIT_FAILURE); } \
} while (0)
#define IP "127.0.0.1"
#define SIZE 100
#define QUEUE_LENGTH 1
int main (int argc, char *argv [])
{
/* test arg number */
if(argc!=3){
fprintf(stderr, "usage: %s ip_addr port_number\n", argv[0]);
exit(EXIT_FAILURE);
}
/* convert and check port number */
int port_number = atoi(argv[2]);
if(port_number < 10000 || port_number > 65000){
fprintf(stderr,"port number\n");
exit(EXIT_FAILURE);
}
/* create socket */
int new_socket;
CHECK(new_socket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP));
/* SO_REUSEADDR option allows re-starting the program without delay */
int iSetOption = 1;
CHECK (setsockopt (new_socket, SOL_SOCKET, SO_REUSEADDR, &iSetOption,
sizeof iSetOption));
/* complete struct sockaddr */
struct addrinfo hints, *serv_dest;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET; // Use IPv4
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_NUMERICHOST;
int error = getaddrinfo(argv[1], argv[2], &hints, &serv_dest);
if (error != 0) {
fprintf(stderr,"getaddrinfo %s\n",gai_strerror(error)); // Generic error message
exit(1); // Set the exit status to 1
}
/* link socket to local IP and PORT */
CHECK(bind(new_socket,serv_dest->ai_addr,serv_dest->ai_addrlen));
/* set queue limit for incoming connections */
CHECK(listen(new_socket,QUEUE_LENGTH));
/* wait for incoming TCP connection */
struct sockaddr_storage client;
socklen_t size = sizeof(client);
int client_sock=accept(new_socket,(struct sockaddr*)&client, &size);
CHECK(client_sock);
/* print sender addr and port */
char host[NI_MAXHOST];
char service[NI_MAXSERV];
if (getnameinfo((struct sockaddr*)&client,size, host, NI_MAXHOST, service, NI_MAXSERV, NI_NUMERICHOST | NI_NUMERICSERV)) {
fprintf(stderr, "getnameinfo: %s\n", gai_strerror(error));
}
printf("%s %s\n", host, service);
/* wait for incoming message */
char buffer[SIZE+1]={0};
memset(buffer, 0, sizeof(buffer));
CHECK(recv(client_sock, buffer, SIZE, 0));
/* close sockets */
close(client_sock);
close(new_socket);
/* free memory */
freeaddrinfo(serv_dest);
/* print received message */
fprintf(stdout, "%s", buffer);
return 0;
}
#!/bin/bash
PROG="./receiver-tcp"
FILE="$PROG.c"
PORT=`shuf -i 10000-65000 -n 1`
PORT_C=`shuf -i 10000-65000 -n 1`
IP="127.0.0.1"
OUT="/tmp/$$"
mkdir $OUT
SE=$(uname -s)
######################################
echo -n "test 01 - program usage: "
echo "usage: $PROG ip_addr port_number" > $OUT/usage
$PROG > $OUT/stdout 2> $OUT/stderr && echo "KO -> exit status $? instead of 1" && exit 1
! cmp -s $OUT/usage $OUT/stderr && echo "KO -> unexpected output on stderr => check file \"$OUT/usage\"" && exit 1
[ -s $OUT/stdout ] && echo "KO -> output detected on stdout" && exit 1
$PROG a > $OUT/stdout 2> $OUT/stderr && echo "KO -> exit status $? instead of 1" && exit 1
! cmp -s $OUT/usage $OUT/stderr && echo "KO -> unexpected output on stderr => check file \"$OUT/usage\"" && exit 1
[ -s $OUT/stdout ] && echo "KO -> output detected on stdout" && exit 1
$PROG a b c > $OUT/stdout 2> $OUT/stderr && echo "KO -> exit status $? instead of 1" && exit 1
! cmp -s $OUT/usage $OUT/stderr && echo "KO -> unexpected output on stderr => check file \"$OUT/usage\"" && exit 1
[ -s $OUT/stdout ] && echo "KO -> output detected on stdout" && exit 1
echo "....................OK"
######################################
echo -n "test 02 - no dynamic memory allocation: "
grep -q "[cm]alloc" $FILE && echo "KO -> dynamic memory allocation is not allowed!" && exit 1
echo ".....OK"
######################################
echo -n "test 03 - invalid port number: "
$PROG a 65001 > $OUT/stdout 2> $OUT/stderr && echo "KO -> exit status $? instead of 1" && exit 1
! [ -s $OUT/stderr ] && echo "KO -> no output on stderr" && exit 1
[ -s $OUT/stdout ] && echo "KO -> output detected on stdout" && exit 1
$PROG a 9999 > $OUT/stdout 2> $OUT/stderr && echo "KO -> exit status $? instead of 1" && exit 1
! [ -s $OUT/stderr ] && echo "KO -> no output on stderr" && exit 1
[ -s $OUT/stdout ] && echo "KO -> output detected on stdout" && exit 1
echo "..............OK"
######################################
echo -n "test 04 - getaddrinfo usage: "
ERROR="Name or service not known"
if [ "$SE" == "Darwin" ]; then
ERROR="nodename nor servname provided, or not known"
fi
LC_ALL=C $PROG a $PORT > $OUT/stdout 2> $OUT/stderr && echo "KO -> exit status $? instead of 1" && exit 1
! grep -q "$ERROR" $OUT/stderr && echo "KO -> unexpected output on stderr, you should use gai_strerror" && exit 1
grep -q "$ERROR" $FILE && echo "KO -> no static msg for getaddrinfo error" && exit 1
[ -s $OUT/stdout ] && echo "KO -> output detected on stdout" && exit 1
echo "................OK"
######################################
echo -n "test 05 - check bind failure: "
timeout 8 nc -4l $IP $PORT > /dev/null 2>&1 &
TO=$!
sleep 3
LC_ALL=C timeout 5 $PROG $IP $PORT > $OUT/stdout 2> $OUT/stderr
R="$?"
[ "$R" == "124" ] && echo "KO -> program times out" && exit 1
[ "$R" != "1" ] && echo "KO -> exit status $R instead of 1" && exit 1
wait $!
ERROR="Address already in use"
! grep -q "$ERROR" $OUT/stderr && echo "KO -> unexpected output on stderr, you should use perror" && exit 1
grep -q "$ERROR" $FILE && echo "KO -> no static msg for bind error" && exit 1
[ -s $OUT/stdout ] && echo "KO -> output detected on stdout" && exit 1
echo "...............OK"
######################################
echo -n "test 06 - sender info in sockaddr_storage: "
! grep -q "sockaddr_storage" $FILE && echo "KO -> sockaddr_storage not found" && exit 1
grep -q "sockaddr_in" $FILE && echo "KO -> remove sockaddr_in" && exit 1
echo "..OK"
######################################
echo -n "test 07 - program exits without error: "
timeout 5 $PROG $IP $PORT > $OUT/stdout 2> $OUT/stderr &
TO=$!
sleep 2
timeout 1 echo "hello world" | nc -4 $IP $PORT -p $PORT_C
wait $TO
R=$?
[ "$R" == "124" ] && echo "KO -> program times out" && exit 1
[ "$R" != "0" ] && echo "KO -> exit status $R instead of 0" && exit 1
[ -s $OUT/stderr ] && echo "KO -> output detected on stderr" && exit 1
echo "......OK"
######################################
echo -n "test 08 - program prints sender IP and port: "
PRINTEDIP=`cat $OUT/stdout | head -n 1`
[ -z "$PRINTEDIP" ] && echo "KO -> nothing on stdout" && exit 1
echo "OK"
######################################
echo -n "test 09 - printed IP is valid: "
PRINTEDIP=`cat $OUT/stdout | head -n 1 | cut -d ' ' -f1`
[ "$PRINTEDIP" != "$IP" ] && echo "KO -> printed IP: $PRINTEDIP | expected: $IP" && exit 1
echo "..............OK"
######################################
echo -n "test 10 - printed PORT is valid: "
PRINTEDPORT=`cat $OUT/stdout | head -n 1 | cut -d ' ' -f2`
[ "$PRINTEDPORT" != "$PORT_C" ] && echo "KO -> printed PORT: $PRINTEDPORT | expected: $PORT_C" && exit 1
echo "............OK"
######################################
echo -n "test 11 - program received a message: "
NB=`cat $OUT/stdout | wc -l | tr -s ' ' | cut -d ' ' -f2`
[ "$NB" != "2" ] && echo "KO -> no message received" && exit 1
echo ".......OK"
######################################
echo -n "test 12 - received message is valid: "
MES=`cat $OUT/stdout | tail -1`
[ "$MES" != "hello world" ] && echo "KO -> message received: $MES | expected: hello world" && exit 1
echo "........OK"
######################################
echo -n "test 13 - program accept up to SIZE bytes: "
! grep -q "#define SIZE" $FILE && echo "KO -> no SIZE defined" && exit 1
SIZE=$(grep "SIZE" $FILE | cut -d ' ' -f3)
LC_ALL=C tr -dc "A-Za-z0-9" < /dev/urandom | head -c $SIZE > $OUT/toto
timeout 5 $PROG $IP $PORT > $OUT/stdout 2> $OUT/stderr &
TO=$!
sleep 2
nc -4 $IP $PORT < $OUT/toto
wait $TO
R=$?
[ "$R" == "124" ] && echo "KO -> program times out" && exit 1
[ "$R" != "0" ] && echo "KO -> exit status $R instead of 0" && exit 1
[ -s $OUT/stderr ] && echo "KO -> output detected on stderr" && exit 1
NB=$(tail -1 $OUT/stdout | wc -c | tr -s ' ' | cut -d ' ' -f2)
[ "$NB" != "$SIZE" ] && echo "KO -> only \"$NB\" bytes printed (\"$SIZE\" expected)" && exit 1
echo "..OK"
######################################
echo -n "test 14 - memory error: "
P=`which valgrind`
[ -z "$P" ] && echo "KO -> please install valgrind" && exit 1
LC_ALL=C tr -dc "A-Za-z0-9" < /dev/urandom | head -c 2049 > $OUT/toto
valgrind --leak-check=full --error-exitcode=100 --log-file=$OUT/valgrind.log $PROG $IP $PORT > /dev/null &
V=$!
sleep 3
nc -4 -w1 $IP $PORT < $OUT/toto
wait $V
[ "$?" == "100" ] && echo "KO -> memory pb please check valgrind.log" && exit 1
echo ".....................OK"
rm -r $OUT
stages:
- build
- test
image: montavont/algodesreseaux
build_09:
stage: build
script:
- cd 09-transfert-fichierTCPv6-source
- scons
artifacts:
paths:
- 09-transfert-fichierTCPv6-source/sender-tcp
# run tests using the binary build before
test_09:
stage: test
needs: [build_09]
script:
- |
echo "starting tests"
cd 09-transfert-fichierTCPv6-source
bash tests.sh
File added
# Algorithmes des réseaux
## Transfert de fichier TCP - expéditeur
Complétez le programme `sender-tcp.c` pour transmettre le contenu d'un fichier avec les protocoles IPv6 et TCP.
Le programme `sender-tcp` admet en argument le numéro de port de l'hôte distant à contacter et le nom du fichier à transmettre :
./sender-udp port_number fichier_a_transmettre
Ce programme devra copier les données lues depuis le fichier passé en argument le socket à destination de l'hôte distant.
Les seuls numéros de port valides sont ceux contenus dans l'intervalle `[10000; 65000]`.
**Objectifs :** savoir transmettre un fichier avec le protocole TCP.
## Marche à suivre
Vous pouvez reprendre une grande partie du code produit pour l'exercice 7. Vous devez simplement mettre à jour les parties spécifiques à IPv4. Le programme `sender-tcp` devra transmettre les données du fichier vers l'adresse `::1` et le port passé en argument du programme.
La copie (depuis le fichier source vers le socket) devra être réalisée par la fonction à compléter :
void cpy (int in, int out)
`in` et `out` sont respectivement les descripteurs sur lesquels lire et écrire.
Pour tester votre programme, vous pouvez lancez dans un terminal la commande suivante :
nc -6l ::1 PORT > copy.tmp
`PORT` est le numéro de port qui sera passé à votre programme. Exécutez ensuite votre programme et vérifiez que le fichier source et sa copie `copy.tmp` sont identiques via le commande suivante :
cmp fichier_a_transmettre copy.tmp
On vous conseille de tester votre programme sur de gros fichiers binaires.
# Validation
Votre programme doit obligatoirement passer tous les tests sur gitlab (il suffit de `commit/push` le fichier source pour déclencher le pipeline de compilation et de tests) avant de passer à l'exercice suivant.
sources = Glob ("*.c")
CFLAGS = ["-Wall", "-Wextra", "-Werror", "-g"]
env = Environment (CCFLAGS = CFLAGS)
env.Program (sources)
#include <sys/socket.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <netdb.h>
#define CHECK(op) do { if ( (op) == -1) { perror (#op); exit (EXIT_FAILURE); } \
} while (0)
#define IP "::1"
#define SIZE 100
void cpy (int src, int dst)
{
char buffer[1024];
// Read from the source descriptor
ssize_t bytesRead;
while ((bytesRead = read(src, buffer, sizeof(buffer))) > 0) {
ssize_t bytesWritten = write(dst, buffer, bytesRead);
if (bytesWritten == -1) {
perror("Error writing to destination descriptor");
exit(EXIT_FAILURE);
}
}
}
int main (int argc, char *argv [])
{
/* test arg number */
if(argc != 4){
fprintf(stderr,"usage: %s ip_addr port_number filename\n",argv[0]);
exit(EXIT_FAILURE);
}
/* convert and check port number */
int port_number = (atoi(argv[2]));
if(port_number < 10000 || port_number > 65000){
fprintf(stderr,"port\n");
exit(EXIT_FAILURE);
}
/* open file to send */
int file;
CHECK(file = open(argv[3],O_RDONLY,0666));
/* create socket */
int new_socket = socket(AF_INET6,SOCK_STREAM,IPPROTO_TCP);
CHECK(new_socket);
/* complete struct sockaddr */
struct addrinfo hints, *res;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET6; // Use IPv4
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
int error = getaddrinfo(argv[1], argv[2], &hints, &res);
if (error != 0) {
fprintf(stderr,"getaddrinfo %s\n",gai_strerror(error)); // Generic error message
exit(1); // Set the exit status to 1
}
/* connect to the remote peer */
struct sockaddr_in6 server_address;
memset(&server_address, 0, sizeof(server_address));
server_address.sin6_family = AF_INET6;
server_address.sin6_port = htons(port_number);
CHECK(connect(new_socket, (struct sockaddr *)&server_address, sizeof(server_address)));
cpy(file,new_socket);
/* close socket */
CHECK(close(new_socket));
/* close file */
CHECK(close(file));
/* free memory */
freeaddrinfo(res);
return 0;
}
#!/bin/bash
PROG="./sender-tcp"
FILE="$PROG.c"
PORT=`shuf -i 10000-65000 -n 1`
OUT="/tmp/$$"
mkdir $OUT
IP="::1"
SE=$(uname -s)
######################################
echo -n "test 01 - program usage: "
echo "usage: $PROG ip_addr port_number filename" > $OUT/usage
$PROG > $OUT/stdout 2> $OUT/stderr && echo "KO -> exit status $? instead of 1" && exit 1
! cmp -s $OUT/usage $OUT/stderr && echo "KO -> unexpected output on stderr => check file \"$OUT/usage\"" && exit 1
[ -s $OUT/stdout ] && echo "KO -> output detected on stdout" && exit 1
$PROG a > $OUT/stdout 2> $OUT/stderr && echo "KO -> exit status $? instead of 1" && exit 1
! cmp -s $OUT/usage $OUT/stderr && echo "KO -> unexpected output on stderr => check file \"$OUT/usage\"" && exit 1
[ -s $OUT/stdout ] && echo "KO -> output detected on stdout" && exit 1
$PROG a b > $OUT/stdout 2> $OUT/stderr && echo "KO -> exit status $? instead of 1" && exit 1
! cmp -s $OUT/usage $OUT/stderr && echo "KO -> unexpected output on stderr => check file \"$OUT/usage\"" && exit 1
[ -s $OUT/stdout ] && echo "KO -> output detected on stdout" && exit 1
$PROG a b c d > $OUT/stdout 2> $OUT/stderr && echo "KO -> exit status $? instead of 1" && exit 1
! cmp -s $OUT/usage $OUT/stderr && echo "KO -> unexpected output on stderr => check file \"$OUT/usage\"" && exit 1
[ -s $OUT/stdout ] && echo "KO -> output detected on stdout" && exit 1
echo ".......................OK"
######################################
echo -n "test 02 - no dynamic memory allocation: "
grep -q "[cm]alloc" $FILE && echo "KO -> dynamic memory allocation is not allowed!" && exit 1
echo "........OK"
######################################
echo -n "test 03 - invalid port number: "
$PROG a 65001 file > $OUT/stdout 2> $OUT/stderr && echo "KO -> exit status $? instead of 1" && exit 1
! [ -s $OUT/stderr ] && echo "KO -> no output on stderr" && exit 1
[ -s $OUT/stdout ] && echo "KO -> output detected on stdout" && exit 1
$PROG a 9999 file > $OUT/stdout 2> $OUT/stderr && echo "KO -> exit status $? instead of 1" && exit 1
! [ -s $OUT/stderr ] && echo "KO -> no output on stderr" && exit 1
[ -s $OUT/stdout ] && echo "KO -> output detected on stdout" && exit 1
echo ".................OK"
######################################
echo -n "test 04 - program with unknown filename: "
$PROG $IP $PORT abcd > $OUT/stdout 2> $OUT/stderr && echo "KO -> exit status $? instead of 1" && exit 1
! [ -s $OUT/stderr ] && echo "KO -> no output on stderr" && exit 1
[ -s $OUT/stdout ] && echo "KO -> output detected on stdout" && exit 1
echo ".......OK"
######################################
echo -n "test 05 - getaddrinfo usage: "
ERROR="Name or service not known"
if [ "$SE" == "Darwin" ]; then
ERROR="nodename nor servname provided, or not known"
fi
LC_ALL=C $PROG a $PORT README.md > $OUT/stdout 2> $OUT/stderr && echo "KO -> exit status $? instead of 1" && exit 1
! grep -q "$ERROR" $OUT/stderr && echo "KO -> unexpected output on stderr, you should use gai_strerror" && exit 1
grep -q "$ERROR" $FILE && echo "KO -> no static msg for getaddrinfo error" && exit 1
[ -s $OUT/stdout ] && echo "KO -> output detected on stdout" && exit 1
echo "...................OK"
######################################
echo -n "test 06 - connection to a non listening server: "
$PROG $IP $PORT $PROG > $OUT/stdout 2> $OUT/stderr && echo "KO -> exit status $?" && exit 1
[ -s $OUT/stdout ] && echo "KO -> output detected on stdout" && exit 1
! [ -s $OUT/stderr ] && echo "KO -> no output on stderr" && exit 1
echo "OK"
######################################
echo -n "test 07 - program exits without error: "
timeout 10 nc -6l $IP $PORT > $OUT/copy.tmp &
TO=$!
sleep 4
! $PROG $IP $PORT $PROG > $OUT/stdout 2> $OUT/stderr && echo "KO -> exit status $?" && exit 1
wait $TO
[ -s $OUT/stderr ] && echo "KO -> output detected on stderr" && exit 1
echo ".........OK"
######################################
echo -n "test 08 - program sent some data: "
! [ -s $OUT/copy.tmp ] && echo "KO -> no data (empty file at the receiver)" && exit 1
echo "..............OK"
######################################
echo -n "test 09 - files are the same: "
! cmp -s $PROG $OUT/copy.tmp && echo "KO -> files differ, check \"$PROG\" (transmitted) and \"$OUT/copy.tmp\" (received)" && exit 1
echo "..................OK"
######################################
echo -n "test 10 - execution time: "
head -c 1024300 /dev/urandom > $OUT/toto_b
timeout 10 nc -6l $IP $PORT > $OUT/copy.tmp &
TO=$!
sleep 4
t0=`date +%s`
$PROG $IP $PORT $OUT/toto_b > $OUT/stdout 2> $OUT/stderr
t1=`date +%s`
runtime=$(($t1 - $t0))
[ $runtime -gt "1" ] && echo "KO -> execution time exceeds 1sec., you should read by blocks" && exit 1
[ -s $OUT/stdout ] && echo "KO -> output detected on stdout" && exit 1
[ -s $OUT/stderr ] && echo "KO -> output detected on stderr" && exit 1
wait $TO
echo "......................OK"
######################################
echo -n "test 11 - memory error: "
P=`which valgrind`
[ -z "$P" ] && echo "KO -> please install valgrind" && exit 1
timeout 10 nc -6l $IP $PORT > $OUT/copy.tmp &
sleep 4
valgrind --leak-check=full --error-exitcode=100 --log-file=$OUT/valgrind.log $PROG $IP $PORT $OUT/toto_b > /dev/null 2>&1
[ "$?" == "100" ] && echo "KO -> memory pb please check $OUT/valgrind.log" && exit 1
echo "........................OK"
######################################
rm -r $OUT
stages:
- build
- test
image: registry.app.unistra.fr/montavont/img-docker/image
build_10:
stage: build
script:
- cd 10-transfert-fichierTCPv6-dest
- scons
artifacts:
paths:
- 10-transfert-fichierTCPv6-dest/receiver-tcp
# run tests using the binary build before
test_10:
stage: test
needs: [build_10]
script:
- |
echo "starting tests"
cd 10-transfert-fichierTCPv6-dest
bash tests.sh
# Algorithmes des réseaux
## Transfert de fichier TCP - destinataire
Complétez le programme `receiver-tcp.c` pour recevoir le contenu d'un fichier avec les protocoles IPv6 et TCP.
Le programme `receiver-tcp` admet en argument l'adresse IP et le numéro de port sur lequel le programme doit écouter :
./receiver-tcp ip_addr port_number
Ce programme devra copier les données reçues sur le socket dans le fichier `copy.tmp` qui devra être créé dans le répertoire courant.
## Marche à suivre
Vous pouvez reprendre une grande partie du code produit pour l'exercice 8. Vous devez simplement mettre à jour les parties spécifiques à IPv4. Le programme `receiver-tcp` devra écouter sur l'adresse IPv6 et le port passé en argument du programme.
La copie (depuis le socket vers le fichier `copy.tmp`) devra être réalisée par la fonction à compléter :
void cpy (int in, int out)
`in` et `out` sont respectivement les descripteurs sur lesquels lire et écrire. Vous veillerez à lire sur le socket par blocs d'octets pour limiter le nombre d'appels système.
N'oubliez pas d'ajouter le code suivant après la création du socket `sockfd` pour les raisons évoquées dans l'exercice 8 :
Testez votre programme à l'aide du programme réalisé dans l'exercice précédent, ou via la commande :
nc -6 adresse_ip_de_votre_programme port_de_votre_programme < fichier_a_transmettre
Vérifier que le fichier source et sa copie `copy.tmp` sont identiques via la commande `cmp` :
cmp fichier_a_transmettre copy.tmp
# Validation
Votre programme doit obligatoirement passer tous les tests sur gitlab (il suffit de `commit/push` le fichier source pour déclencher le pipeline de compilation et de tests) avant de passer à l'exercice suivant.
sources = Glob ("*.c")
CFLAGS = ["-Wall", "-Wextra", "-Werror", "-g"]
env = Environment (CCFLAGS = CFLAGS)
env.Program (sources)
#include <sys/socket.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <netdb.h>
#define CHECK(op) do { if ( (op) == -1) { perror (#op); exit (EXIT_FAILURE); } \
} while (0)
#define IP "::1"
#define SIZE 100
#define QUEUE_LENGTH 1
void cpy (int src, int dst)
{
char buffer[1024];
// Read from the source descriptor
ssize_t bytesRead;
while ((bytesRead = read(src, buffer, sizeof(buffer))) > 0) {
ssize_t bytesWritten = write(dst, buffer, bytesRead);
if (bytesWritten == -1) {
perror("Error writing to destination descriptor");
exit(EXIT_FAILURE);
}
}
}
int main (int argc, char *argv [])
{
/* test arg number */
if(argc!=3){
fprintf(stderr, "usage: %s ip_addr port_number\n", argv[0]);
exit(EXIT_FAILURE);
}
/* convert and check port number */
int port_number = atoi(argv[2]);
if(port_number < 10000 || port_number > 65000){
fprintf(stderr,"port number\n");
exit(EXIT_FAILURE);
}
/* create socket */
int new_socket;
CHECK(new_socket = socket(AF_INET6,SOCK_STREAM,IPPROTO_TCP));
/* SO_REUSEADDR option allows re-starting the program without delay */
int iSetOption = 1;
CHECK (setsockopt (new_socket, SOL_SOCKET, SO_REUSEADDR, &iSetOption,
sizeof iSetOption));
/* complete struct sockaddr */
struct addrinfo hints, *serv_dest;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET6; // Use IPv6
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_NUMERICHOST;
int error = getaddrinfo(argv[1], argv[2], &hints, &serv_dest);
if (error != 0) {
fprintf(stderr,"getaddrinfo %s\n",gai_strerror(error)); // Generic error message
exit(1); // Set the exit status to 1
}
/* link socket to local IP and PORT */
CHECK(bind(new_socket,serv_dest->ai_addr,serv_dest->ai_addrlen));
/* set queue limit for incoming connections */
CHECK(listen(new_socket,QUEUE_LENGTH));
/* wait for incoming TCP connection */
struct sockaddr_storage client;
socklen_t size = sizeof(client);
int client_sock=accept(new_socket,(struct sockaddr*)&client, &size);
CHECK(client_sock);
/* open local file */
int local_file;
CHECK(local_file=open("copy.tmp",O_WRONLY|O_CREAT|O_TRUNC));
/* get the transmitted file */
cpy(client_sock,local_file);
/* close sockets */
close(new_socket);
close(client_sock);
/* close file */
close(local_file);
/* free memory */
freeaddrinfo(serv_dest);
return 0;
}