labo 5: Application réseaux et sockets
Directory
Labo 5: applications réseaux socket
1. Makefile
vérification de la présence de make
sol@debian:~/LABOS/labo5$ which make
/usr/bin/make+
s’assurer que c’est GNU make:
sol@debian:~/LABOS/labo5$ dpkg -s make | grep GNU
GNU Make is a utility which controls the generation of executables
affichage du contenu de Makefile
sol@debian:~/LABOS/labo5/makefile$ cat --show-tabs Makefile
DEST_IMAGES=neuchatel.jpeg jura.jpeg
all: $(DEST_IMAGES)
^Ifile *.jpeg
clean:
^Irm -f $(DEST_IMAGES)
neuchatel.jpeg: neuchatel.png
^Ipngtopnm < $< | pnmtojpeg > $@
^I@# n'oubliez pas le TAB � gauche!
^I@echo converti $< en $@
jura.jpeg: jura.png
^Ipngtopnm < $< | pnmtojpeg > $@
^I@# n'oubliez pas le TAB � gauche!
^I@echo converti $< en $@
jeu de char de Makefille
sol@debian:~/LABOS/labo5/makefile$ file -i Makefile
Makefile: text/x-makefile; charset=iso-8859-1
rencodage de Makefille
sol@debian:~/LABOS/labo5/makefile$ recode iso-8859-1..UTF-8 Makefile
sol@debian:~/LABOS/labo5/makefile$ file -i Makefile
Makefile: text/x-makefile; charset=utf-8
sol@debian:~/LABOS/labo5/makefile$ cat --show-tabs Makefile
DEST_IMAGES=neuchatel.jpeg jura.jpeg
all: $(DEST_IMAGES)
^Ifile *.jpeg
clean:
^Irm -f $(DEST_IMAGES)
neuchatel.jpeg: neuchatel.png
^Ipngtopnm < $< | pnmtojpeg > $@
^I@# n'oubliez pas le TAB à gauche!
^I@echo converti $< en $@
jura.jpeg: jura.png
^Ipngtopnm < $< | pnmtojpeg > $@
^I@# n'oubliez pas le TAB à gauche!
^I@echo converti $< en $@
L’option --show-tabs
remplace les tabulations par des ^
question 6
tuto makefile
cible: dependance
commandes
$@ # nom de la cible actuelle.
Le Makefille va réencoder les fichier neuchatel.png
et jura.png
en neuchatel.jpg
et jura.jpg
question 7
fichier1.txt: fichier0.txt
cat fichier0.txt > fichier1.txt
Cette règle ne sera exécutée que si fichier0.txt est plus récent que fichier1.txt
Dans notre cas:
neuchatel.jpeg: neuchatel.png
pngtopnm < $< | pnmtojpeg > $@
@# n'oubliez pas le TAB à gauche!
@echo converti $< en $@
La règle n’est pas exécutée car neuchatel.png n’est pas plus récent que neuchatel.jpg et pareil pour jura.png.
question 8
sol@debian:~/LABOS/labo5/makefile$ touch jura.png
sol@debian:~/LABOS/labo5/makefile$ make
pngtopnm < jura.png | pnmtojpeg > jura.jpeg
converti jura.png en jura.jpeg
file *.jpeg
jura.jpeg: JPEG image data, JFIF standard 1.01, aspect ratio, density 1x1, segment length 16, baseline, precision 8, 406x493, frames 3
neuchatel.jpeg: JPEG image data, JFIF standard 1.01, aspect ratio, density 1x1, segment length 16, baseline, precision 8, 406x493, frames 3
Comme touch jura.png
modifie la date de modification de jura.png, il devient donc plus récent que jura.jpeg et la règle…
jura.jpeg: jura.png
pngtopnm < $< | pnmtojpeg > $@
… est denouveau valable.
question 9
@
empêche l’affichage de la sortie texte d’une commande.
question 10
En utilisant le Pattern matching on peut créer des règles génériques pour entre autres convertir les fichiers d’un certain format en un autre:
Make exécute une règle même si le fichier correspondant est situé dans un sous dossier ! En cas de conflit, la règle avec la meilleure correspondance est choisie.
La syntaxe générale d’une fonction:
$(fonction arg0,arg1,arg2...).
La fonction patsubst
remplace par une paterne chaque mot dans une liste donnée.
Normalement les remplacement de Wildcards se font automatiquement dans les règles mais comme dans notre cas on veut utiliser un wildcard en argument d’une fonction on doit utiliser la fonction wildcard
.
syntaxe:
$(patsubst %.c,%.o,$(wildcard *.c))
Élimination de la redondance dans le Makefile original + optimisation via fonctions.
DEST_IMAGES=$(patsubst %.png, %.jpeg, $(wildcard *.png))
all: $(DEST_IMAgES)
file *.jpeg
%.jpeg: %.png
pngtopnm < $< | pnmtojpeg > $@
@echo converti $< en $@
clean:
rm -f $(DEST_IMAGES)
sources:
gnu.org
stackoverflow.com
learnxinyminutes.com
question 11
a.
sol@debian:~/LABOS/labo5/02tests$ make a.o
cc -c -o a.o a.c
sol@debian:~/LABOS/labo5/02tests$ ls
a.c a.o
Si on utilise le -d
de make on a tout un paquet d’informations mais dans les premières lignes on peut voir que c’est bien GCC qui est utilisé pour le build.
sol@debian:~/LABOS/labo5/02tests$ make -d a.o
GNU Make 4.0
Built for x86_64-pc-linux-gnu
# ...
Finished prerequisites of target file 'a.o'.
Must remake target 'a.o'.
cc -c -o a.o a.c
Putting child 0x10dfde0 (a.o) PID 6042 on the chain.
Live child 0x10dfde0 (a.o) PID 6042
Reaping winning child 0x10dfde0 PID 6042
Removing child 0x10dfde0 PID 6042 from chain.
Successfully remade target file 'a.o'.
sol@debian:~/LABOS/labo5/02tests$ ls
a.c a.o
b.
CFLAGS
et CXXFLAGS
sont les noms de variables d’environnement ou de variables du Makefile qui peuvent être utilisées pour paramétrer la compilation d’un logiciel.
Ces variables sont habituellement positionnées dans un Makefile et sont ajoutées quand le compilateur est appelé. Si elles ne sont pas spécifiées dans le Makefile, alors elles seront prises directement à partir de l’environnement, si elles sont présentes.
CFLAGS
permet d’ajouter des paramètres sur la ligne de commande qui appelle le compilateur C, alors que CXXFLAGS
est utilisé pour la compilation C++. De même, une variable similaire, CPPFLAGS
, permet de passer des paramètres sur la ligne de commande du Préprocesseur C.
-
-Wall : -W represents warnings. -Wall means it will show all possible warnings.
-
-O2 : means optimize it. These are used in creating machine code from source code by compiler. There are many levels of optimizing. O2 is generally considered good enough, while O3 makes your file size bigger without optimizing significantly, and may make your program slower on some sort of algorithms.
-
-g means debug. This will add many debugging “notes” in your final binary, so that when you run a debugger on your final binary file, the debugger will know what part of this binary file is related to which part of the original source code.
-
-I/path/to/include/files : In C/C++, you can include files which compiler already knows where they are present with command: #include
. If the compiler does not know where the include file you want to include is present, you can specify the path to that directory with -I/the/directory
Il nous faut donc créer un Makefile et y ajouter une ligne CFLAGS=-Wall
sol@debian:~/LABOS/labo5/02tests$ make -Wall a.o
cc -Wall -c -o a.o a.c
sol@debian:~/LABOS/labo5/02tests$ rm a.0
sol@debian:~/LABOS/labo5/02tests$ make a.o
cc -Wall -c -o a.o a.c
sources:
man gcc
wikipedia.fr
cs.colby.edu
wiki.gentoo.org (très bon site) qui explique ce point en détails
question 12
Voir question 10
question 13
voir question 10
2. Traçage et simulation
netstat -an
voir tous les socketsnetstat -an --inet
sockets ipv4netstat -an --inet6
sockets ipv6netstat --inet --listen -NNNN
socket en écoutenetstat --inet6 --listen -NNNN
socket en écoute- sudo
netstat
-p
… identification du process lié (PID/prgrm name)a
display all sockets
n
numeric: don’t resolve name strace
traçage des appels system-e
expr a qualifying expression: option=[!]all or option=[!]val1[,val2]…strace -e socket,connect ./simple-client localhost 7000
ltrace
traçage des fonctions de la LIBC
création d’un serveur TCP avec netcat:
nc -l -p
portnc
netcat
-l
listen mode, for inbound connects
-p
local port number
penser à utiliser Wireshark !
question 1
Afficher les permissions:
-rwxr-xr-x 1 sol sol 10995 Mar 25 2015 a-tracer
question 2
Lancer et déterminer le PID
sol@debian:~/LABOS/labo5/03Trace$ ./a-tracer &
[1] 6834
question 3
Déterminer les ports en écoute
sol@debian:~$ sudo netstat -p -n --inet --listen 6834
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 453/sshd
tcp 0 0 0.0.0.0:12345 0.0.0.0:* LISTEN 6834/a-tracer
tcp 0 0 127.0.0.1:25 0.0.0.0:* LISTEN 844/exim4
udp 0 0 0.0.0.0:59362 0.0.0.0:* 828/dhclient
udp 0 0 0.0.0.0:68 0.0.0.0:* 828/dhclient
udp 0 0 10.0.2.15:123 0.0.0.0:* 498/ntpd
udp 0 0 127.0.0.1:123 0.0.0.0:* 498/ntpd
udp 0 0 0.0.0.0:123 0.0.0.0:* 498/ntpd
L’option -n
permet de ne pas résoudre les noms.
Notre application utilise donc le port 12345.
question 4
Après un kill du process…
sol@debian:~$ sudo netstat -p -n --inet --listen 6834
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 453/sshd
tcp 0 0 127.0.0.1:25 0.0.0.0:* LISTEN 844/exim4
udp 0 0 0.0.0.0:59362 0.0.0.0:* 828/dhclient
udp 0 0 0.0.0.0:68 0.0.0.0:* 828/dhclient
udp 0 0 10.0.2.15:123 0.0.0.0:* 498/ntpd
udp 0 0 127.0.0.1:123 0.0.0.0:* 498/ntpd
udp 0 0 0.0.0.0:123 0.0.0.0:* 498/ntpd
GONE !
question 5
a.
Dans le cycle de vie des sockets d’un serveur TCP nous retrouvons les fonctions socket() bind() listen() et accept() qui sont utilisées avant l’établissement de la connexion.
///...
socket (PF_INET, SOCK_STREAM, IPPROTO_IP) = 3
setsockopt (3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
bind (3, {sa_family=AF_INET, sin_port=htons(12345), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
listen (3, 1) = 0
accept (3,
Sur cette première capture des appels system nous retrouvons les fonctions nos fonctions:
int socket(int domain, int type, int protocol);
Cette fonction crée un socket. On remarque qu’un socket est donc de type int. Renvoie le descripteur du socket nouvellement créé.
int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen);
Lie un socket avec une structure sockaddr
.
int listen(int s, int backlog);
Définie la taille de la file de connexions en attente pour le socket s
.
int accept(int sock, struct sockaddr *adresse, socklen_t *longueur);
Accepte la connexion d’un socket sur le socket sock
. Le socket aura été préalablement lié avec un port avec la fonction bind
L’argument adresse sera remplie avec les informations du client qui s’est connecté.
Cette fonction retourne un nouveau socket, qui devra être utilisé pour communiquer avec le client.
La dernière fonction est particulièrement révélatrice. C’est bien le cycle de vie d’un serveur TCP qui attend un qu’un client se connecte. D’où le fait que la capture de strace
soie en stand by à cet endroit.
sources
super tuto programmation sockets
b.
Cycle de vie sockets TCP
Il s’agit des appels système utilisés pour:
- créér des sockets
- les lier à un port connu
- accepter des connexions TCP
Les étapes passées dans notre cas sont donc: création et bind
c.
acceptation (c’est la phase la plus hard et la plus longue d’une rupture)
question 6
Après la commande nc localhost 12345
nous avons:
accept(3, Process 7127 attached
<unfinished ...>
[pid 7127] close(3) = 0
[pid 7127] fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 1), ...}) = 0
[pid 7127] mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fe86d900000
[pid 7127] write(1, "7127: new connection from client"..., 547127: new connection from client 127.0.0.1 port 47530
) = 54
[pid 7127] fcntl(4, F_GETFL) = 0x2 (flags O_RDWR)
[pid 7127] brk(0) = 0x245a000
[pid 7127] brk(0x247b000) = 0x247b000
[pid 7127] fstat(4, {st_mode=S_IFSOCK|0777, st_size=0, ...}) = 0
[pid 7127] mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fe86d8ff000
[pid 7127] lseek(4, 0, SEEK_CUR) = -1 ESPIPE (Illegal seek)
[pid 7127] read(4,
a.
si nous reprenons l’explication de la fonction accept():
Accepte la connexion d’un socket sur le socket
sock
. Le socket aura été préalablement lié avec un port avec la fonctionbind
L’argument adresse sera remplie avec les informations du client qui s’est connecté. Cette fonction retourne un nouveau socket, qui devra être utilisé pour communiquer avec le client.
accept(3, Process 7127 attached
nous dit donc que le le process avec le PID 7127 s’est connecté sur le socket 3. pas sur, (poser la question)
ps -u
nous renvoie que le process 7127 est:
sol 7127 0.0 0.0 4220 80 pts/1 S+ 18:01 0:00 ./a-tracer
b.
Ici nous voyons qu’une seconde instance de a-tracer est apparue ce qui colle avec l’explication de la fonction accept()
sol@debian:~/LABOS/labo5/03Trace$ sudo netstat -anp --inet
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 453/sshd
tcp 0 0 0.0.0.0:12345 0.0.0.0:* LISTEN 7125/a-tracer
tcp 0 0 127.0.0.1:25 0.0.0.0:* LISTEN 844/exim4
tcp 0 0 127.0.0.1:12345 127.0.0.1:47534 ESTABLISHED 7127/a-tracer
tcp 0 0 127.0.0.1:12345 127.0.0.1:47533 TIME_WAIT -
tcp 0 0 127.0.0.1:47534 127.0.0.1:12345 ESTABLISHED 7126/nc
udp 0 0 0.0.0.0:59362 0.0.0.0:* 828/dhclient
udp 0 0 0.0.0.0:68 0.0.0.0:* 828/dhclient
udp 0 0 10.0.2.15:123 0.0.0.0:* 498/ntpd
udp 0 0 127.0.0.1:123 0.0.0.0:* 498/ntpd
udp 0 0 0.0.0.0:123 0.0.0.0:* 498/ntpd
c.
Encore une fois, grace à l’explication de la fonction accept()
nous pouvons dire que le processus initial, est toujours en écoute alors que le process qu’elle a retourné s’est lié au socket 47534. Nous le voyons sur le netstat -anp --inet
précédent.
et voilà la dernière ligne qui désigne le process de netcat
propriétaire du socket 47534
tcp 0 0 127.0.0.1:47534 127.0.0.1:12345 ESTABLISHED 7126/nc
d.
Il y a n + 1
= 3 sockets.
a-tracer est donc un server TCP
en mode flux
.
e.
accept()
f.
-f -- follow forks
g.
-p pid -- trace process with process id PID, may be repeated
question 7
si on reprend tout depuis le début en monitorant Loopback
avec Wireshark, c’est la fête:
- création du premier process de a-tracer.
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name cp 0 0 0.0.0.0:12345 0.0.0.0:* LISTEN 7303/a-tracer
- première connexion sur le process de
a-tracer
Le joli 3 way hand shake ! Les adresses IP sont les mêmes comme c’est un une communication entre un client TCP et un client au sein de la même machine.
nous avons tous les informations concernant les ports dans wireshark mais netstat nous donnes ces infos aussi:
tcp 0 0 127.0.0.1:12345 127.0.0.1:47541 ESTABLISHED 7308/a-tracer
tcp 0 0 127.0.0.1:47541 127.0.0.1:12345 ESTABLISHED 7307/nc
7303 reste en écoute
- seconde connexion sur le process de
a-tracer
tcp 0 0 127.0.0.1:12345 127.0.0.1:47545 ESTABLISHED 7343/a-tracer
tcp 0 0 127.0.0.1:47545 127.0.0.1:12345 ESTABLISHED 7342/nc
nouveau 3 hand shake, 7303 fidèle au poste toujours en écoute pendant que son fils fait sa fête au le port 47545
- création d’un nouveau processus
netcat
que nous trackons avecstrace nc localhost 12345
une foule d’informations dont la fin nous montre le début du cycle de vie coté client. Les appels système interessants sont:
socket(PF_INET, SOCK_STREAM, IPPROTO_TCP) = 3
et
connect(3, {sa_family=AF_INET, sin_port=htons(12345), sin_addr=inet_addr("127.0.0.1")}, 16) = 0
question 8
sol@debian:~$ strace -e socket,bind,recvfrom nc -l -p 12345 -u localhost
socket(PF_LOCAL, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 3
socket(PF_LOCAL, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 3
socket(PF_LOCAL, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 3
socket(PF_LOCAL, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 3
socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP) = 3
bind(3, {sa_family=AF_INET, sin_port=htons(12345), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
recvfrom(3, "Wed May 24 19:48:45 CDT 2017\n", 8192, MSG_PEEK, {sa_family=AF_INET, sin_port=htons(46262), sin_addr=inet_addr("127.0.0.1")}, [16]) = 29
invalid connection to [127.0.0.1] from (UNKNOWN) [127.0.0.1] 46262
= -1 ENOENT (No such file or directory)
socket(PF_LOCAL, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 3
connect(3, {sa_family=AF_LOCAL, sun_path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)
socket(PF_LOCAL, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 3
connect(3, {sa_family=AF_LOCAL, sun_path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)
socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP) = 3
connect(3, {sa_family=AF_INET, sin_port=htons(12345), sin_addr=inet_addr("127.0.0.1")}, 16) = 0
write(3, "Wed May 24 19:48:45 CDT 2017\n", 29) = 29
question 9
sol 7303 0.0 0.0 4080 660 pts/1 S+ 18:56 0:00 ./a-tracer
sol 7308 0.0 0.0 0 0 pts/1 Z+ 18:56 0:00 [a-tracer] <defunct>
sol 7342 0.0 0.0 6328 1660 pts/3 S+ 19:15 0:00 nc localhost 12345
sol 7343 0.0 0.0 4220 80 pts/1 S+ 19:15 0:00 ./a-tracer
sol 7390 0.0 0.0 0 0 pts/1 Z+ 19:36 0:00 [a-tracer] <defunct>
Concernant les processus fils terminés qui se transforment en “processus” zombie:
Un soucis de reallocation mémoire après la destruction d’un objet?
3. Client TCP simple (Linux) *pas pour le labo
ressources:
simple-client.c (base)
video explicative
tuto sockets
mon poste sur les sockets
question 3 (a,b,c,d)
Implémentation de la fonction tcp_connect()
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
static FILE *tcp_connect(const char *hostname, const char *port);
int main(int argc, char **argv)
{
int result = EXIT_FAILURE;
if (argc == 3)
{
FILE *f;
if ((f = tcp_connect(argv[1], argv[2])))
{
}
/* else: failure */
}
else
{
fprintf(stderr, "%s remote-host port\n", argv[0]);
fprintf(stderr, "%s: bad args.\n", argv[0]);
}
return result;
}
static FILE *tcp_connect(const char *hostname, const char *port)
{
FILE *f = NULL;
int s;
struct addrinfo hints;
struct addrinfo *result, *rp;
hints.ai_family = AF_UNSPEC; /* IPv4 or v6 */
hints.ai_socktype = SOCK_STREAM; /* TCP */
hints.ai_flags = 0;
hints.ai_protocol = 0; /* any protocol */
if ((s = getaddrinfo(hostname, port, &hints, &result)))
{
fprintf(stderr, "getaddrinfo(): failed: %s.\n", gai_strerror(s));
}
else
{
for (rp = result; rp != NULL; rp = rp->ai_next)
{
// AFFICHER UNE ADR
char ipname[INET_ADDRSTRLEN];
char servicename[6]; /* "65535\0" */
if (!getnameinfo(rp->ai_addr,
rp->ai_addrlen,
ipname,
sizeof(ipname),
servicename,
sizeof(servicename),
NI_NUMERICHOST|NI_NUMERICSERV))
{
printf("Trying connection to host %s:%s ...\n",
ipname,
servicename);
/* CREATION DE SOCKET */
if ((s = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol)) == -1)
{
perror("socket() failed: ");
}
/* connexion */
if (connect(s, rp->ai_addr, rp->ai_addrlen) != -1)
{
// ...
}
else
{
perror("connect(): ");
}
}
}
freeaddrinfo(result);
}
return f;
}
questions 4,5,6,7
Appels système: après strace -e socket,connect ./simple-client localhost 7000
socket(PF_LOCAL, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 3
connect(3, {sa_family=AF_LOCAL, sun_path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)
socket(PF_LOCAL, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 3
connect(3, {sa_family=AF_LOCAL, sun_path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)
socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE) = 3
socket(PF_INET, SOCK_DGRAM, IPPROTO_IP) = 3
connect(3, {sa_family=AF_INET, sin_port=htons(7000), sin_addr=inet_addr("127.0.0.1")}, 16) = 0
socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP) = 3
connect(3, { sa_family=AF_INET6,
sin6_port=htons(7000),
inet_pton(AF_INET6, "::1", &sin6_addr),
sin6_flowinfo=0,
sin6_scope_id=0
},
28
)
= 0
Trying connection to host ::1:7000 ...
socket(PF_INET6, SOCK_STREAM, IPPROTO_TCP) = 3
connect(
3, { sa_family=AF_INET6,
sin6_port=htons(7000),
inet_pton(AF_INET6, "::1", &sin6_addr),
sin6_flowinfo=0, sin6_scope_id=0
}, 28
)
= -1 ECONNREFUSED (Connection refused)
connect(): : Connection refused
Trying connection to host 127.0.0.1:7000 ...
socket(PF_INET, SOCK_STREAM, IPPROTO_TCP) = 4
connect(4, {sa_family=AF_INET, sin_port=htons(7000), sin_addr=inet_addr("127.0.0.1")}, 16) = 0
+++ exited with 1 +++
fonctions de la libc utilisées après
ltrace ./simple-client localhost 7000
__libc_start_main(0x4007a6, 3, 0x7ffe79c13c18, 0x4009b0 <unfinished ...>
getaddrinfo("localhost", "7000", 0x7ffe79c13ab0, 0x7ffe79c13aa8) = 0
getnameinfo(0x16271e0, 28, "", 16, "Xg\356w\355\177", 6, 3) = 0
printf("Trying connection to host %s:%s "..., "::1",
"7000"Trying connection to host ::1:7000 ... ) = 39
socket(10, 1, 6) = 3
connect(3, 0x16271e0, 28, 0x16271e0) = -1
perror("connect(): "connect(): : Connection refused)
= <void>
getnameinfo(0x1627190, 16, "::1", 16, "7000", 6, 3) = 0
printf("Trying connection to host %s:%s "..., "127.0.0.1",
"7000"Trying connection to host 127.0.0.1:7000 ...)
= 45
socket(2, 1, 6) = 4
connect(4, 0x1627190, 16, 0x1627190) = -1
perror("connect(): "connect(): : Connection refused)
= <void>
freeaddrinfo(0x16271b0) = <void>
+++ exited (status 1) +++
question 8
Ajout d’un printf() dans notre code affin d’avoir un affichage brut de la valeur du champ sin_port:
// ...
// printf("Trying connection to host %s:%s ...\n",
// ipname,
// servicename);
// affichage brut de la valeur du champ sin_port
if (rp->ai_family == AF_INET)
{
printf("HACK: raw port number: %d\n",
((struct sockaddr_in * ) rp->ai_addr) ->sin_port);
} // cast
// CREATION DE SOCKET
// ...
Qui donne le résultat suivant:
sol@debian:~/LABOS/labo5/04CliendTCP/01simple-client$ ./simple-client localhost 7000
Trying connection to host ::1:7000 ...
connect(): : Connection refused
Trying connection to host 127.0.0.1:7000 ...
HACK: raw port number: 22555
Nous avons effectivement une situation étrange. L’affichage de brut de notre port est différent de celui que nous avons introduit pourtant il se connecte bien sur le port 7000 et non le 22555.
On dirait donc que c’est un problème d’affichage du dans le bout de code que nous venons d’introduire.
La lecture du man de getaddrinfo était particulièrement interessante. Non seulement je crois avoir compris que c’est un problème d’endianess mais on y trouve un exemple d’un serveur et d’un client UDP.
endianess:
L’ordre dans lequel les octets sont organisés dans une case mémoire ou dans une communication. Big endian et Little endian sont deux architectures différentes.
Big endian
byte de poids fort à gauche.
Rangement en mémoire de la valeur 0xA0B70708
dans une structure mémoire de cases de 1 byte
adr: | 0 | 1 | 2 | 3 | |
---|---|---|---|---|---|
val: | A0 | B7 | 07 | 08 | … |
Rangement en mémoire de la valeur 0xA0B70708
dans une structure mémoire de cases de 2 byte:
0 | 1 | 2 | 3 | ||
---|---|---|---|---|---|
A0 | B7 | 07 | 08 | … |
Tous les protocoles TCP/IP communiquent en big-endian
Little endian
byte de poid faible à gauche.
Rangement en mémoire de la valeur 0xA0B70708
dans une structure mémoire de cases de 1 byte
adr: | 0 | 1 | 2 | 3 | |
---|---|---|---|---|---|
val: | 08 | 07 | B7 | A0 | … |
Rangement en mémoire de la valeur 0xA0B70708
dans une structure mémoire de cases de 2 byte:
0 | 1 | 2 | 3 | ||
---|---|---|---|---|---|
07 | 08 | A0 | B7 | … |
X86 ainsi que la majorité des pc actuels fonctionne en Little endian
reponse
- 7000 = 0x 1B 58
- 22555 = 0x 58 1B
Bingo ! C’est donc bien un problème d’endianess.
question 9
a, b, c:
-
conversion d’un socket en fichier text dans la fonction tcp_connect()
-
ajout d’un buffer et d’un fgets() dans le main qui va capturer la première ligne de ce que le serveur a à nous dire et puts() l’ajouter dans
buff
.
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
static FILE *tcp_connect(const char *hostname, const char *port);
int main(int argc, char **argv)
{
int result = EXIT_FAILURE;
if (argc == 3)
{
FILE *f;
if ((f = tcp_connect(argv[1], argv[2])))
{
// creation d'un buffer affin de stocker la premiere ligne
char buff[1024];
if (fgets(buff, sizeof(buff), f))
{
puts(buff);
}
}
// else: fail
}
else
{
fprintf(stderr, "%s remote-host port\n", argv[0]);
fprintf(stderr, "%s: bad args.\n", argv[0]);
}
return result;
}
static FILE *tcp_connect(const char *hostname, const char *port)
{
FILE *f = NULL;
int s;
struct addrinfo hints;
struct addrinfo *result, *rp;
hints.ai_family = AF_UNSPEC; // IPv4 or v6
hints.ai_socktype = SOCK_STREAM; // TCP
hints.ai_flags = 0;
hints.ai_protocol = 0; // any protocol
if ((s = getaddrinfo(hostname, port, &hints, &result)))
{
fprintf(stderr, "getaddrinfo(): failed: %s.\n", gai_strerror(s));
}
else
{
for (rp = result; rp != NULL; rp = rp->ai_next)
{
// AFFICHER UNE ADR
char ipname[INET_ADDRSTRLEN];
char servicename[6]; // "65535\0"
if (!getnameinfo(rp->ai_addr, rp->ai_addrlen,
ipname, sizeof(ipname),
servicename, sizeof(servicename),
NI_NUMERICHOST|NI_NUMERICSERV))
{
printf("Trying connection to host %s:%s ...\n",
ipname,
servicename);
// // affichage brut de la valeur du champ sin_port
// if (rp->ai_family == AF_INET)
// {
// printf("HACK: raw port number: %d\n",
// ((struct sockaddr_in * ) rp->ai_addr)->sin_port);
// } // cast
// CREATION DE SOCKET
if ((s = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol)) == -1)
{
perror("socket() failed: ");
}
// CONNEXION
else
{
if (connect(s, rp->ai_addr, rp->ai_addrlen) != -1)
{
// conversion de socket en un FILE *
// fdopen() permet d'utiliser un socket comme un fichier
if ((f = fdopen(s, "r+")))
{ // utilisation de fonctions classiques maintenant possible
break; // !! Si on return maintenant, la memoire n'est pas liberee!
}
}
else
{
perror("connect(): ");
}
close(s); // new -> delete | open -> close
}
}
}
freeaddrinfo(result);
}
return f;
}
4. Client HTTP
Pour pas abuser sur la longueur de la page, Le détail de comment en arriver à ce code se trouve sur une autre page
#include<stdio.h>
#include<string.h>
#include<sys/socket.h>
#include<netdb.h> // hostent
#include<arpa/inet.h> // inet_addr
#include<unistd.h>
bool resolveDomain(char *ip);
int main(int argc, char **argv)
{
// test arguments
if (argc != 3)
{
fprintf(stderr, "\n%s bad args\n", argv[0]);
return 1;
}
// variables
int _socket;
char* _message;
char _buffer[4096];
struct sockaddr_in _server;
// s'assure qu'argv[1] est une IP notation point.
resolveDomain(argv[1]);
// creation du socket
_socket = socket(AF_INET, SOCK_STREAM, 0); // ipv4, TCP, 0 (=IPPROTO_IP)
if (_socket == -1)
{
puts("Could not create socket");
}
// initialisation des membres de l'objet _server
_server.sin_addr.s_addr = inet_addr(argv[1]); // ip
_server.sin_family = AF_INET; // ipv4
_server.sin_port = htons(80); // port (client HTTP = port 80)
// connexion au serveur
if (connect(_socket, (struct sockaddr *)&_server, sizeof(_server)) < 0)
{
puts("connection error\n");
return 1;
}
puts("\nconnected\n");
// envoie requete HTTP
_message = "GET / HTTP/1.1\r\n\r\n";
if (send(_socket, _message, strlen(_message), 0) < 0)
{
puts("Send failed\n");
return 1;
}
// reponse du serveur
if (recv(_socket, _buffer, 4096, 0) < 0)
{
puts("recv failed");
}
puts("Reply received\n");
puts(_buffer);
close(_socket);
return 0;
}
bool resolveDomain(char *ip)
{
char *_hostname = ip;
struct hostent *_he;
struct in_addr **_addr_list;
if ((_he = gethostbyname(_hostname)) == NULL)
{
puts("gethostbyname error");
return 1;
}
_addr_list = (struct in_addr ** )_he->h_addr_list;
strcpy(ip, inet_ntoa(*_addr_list[0]));
return 0;
}
sol@debian:~/code/sockets/clientHTTP-Lab5$ ./clientHTTPv1 gandalf.teleinf.labinfo.eiaj.ch 80
connected
Reply received
HTTP/1.1 400 Bad Request
Date: Tue, 30 May 2017 12:33:58 GMT
Server: Apache/2.2.22 (Debian)
Vary: Accept-Encoding
Content-Length: 301
Connection: close
Content-Type: text/html; charset=iso-8859-1
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>400 Bad Request</title>
</head><body>
<h1>Bad Request</h1>
<p>Your browser sent a request that this server could not understand.<br />
</p>
<hr>
<address>Apache/2.2.22 (Debian) Server at _default_ Port 80</address>
</body></html>
sol@debian:~/code/sockets/clientHTTP-Lab5$ ./clientHTTPv1 www.google.com 80
connected
Reply received
HTTP/1.1 302 Found
Cache-Control: private
Content-Type: text/html; charset=UTF-8
Referrer-Policy: no-referrer
Location: http://www.google.ch/?gfe_rd=cr&ei=1GYtWbrcHqTC8gfh2q6ABA
Content-Length: 258
Date: Tue, 30 May 2017 12:34:28 GMT
<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">
<TITLE>302 Moved</TITLE></HEAD><BODY>
<H1>302 Moved</H1>
The document has moved
<A HREF="http://www.google.ch/?gfe_rd=cr&ei=1GYtWbrcHqTC8gfh2q6ABA">here</A>.
</BODY></HTML>
5. serveur TCP multiprocessus
Pour pas abuser sur la longueur de la page, Le détail de comment en arriver à ce code se trouvent sur une autre page
#include<stdio.h>
#include <stdlib.h>
#include<string.h>
#include<sys/socket.h>
#include<netdb.h>
#include<arpa/inet.h>
#include<unistd.h>
int main(int argc, char **argv)
{
// variables
int _socket, _newSock, c;
struct sockaddr_in _server, _client;
char* _message;
// creation de socket
_socket = socket(AF_INET, SOCK_STREAM, 0);
if (_socket == -1)
{
printf("\nCould not create socket\n");
}
// initialisation des membres de l'objet _server
_server.sin_family = AF_INET;
_server.sin_addr.s_addr = INADDR_ANY;
_server.sin_port = htons(atoi(argv[1])); // conversion (char->int)->NetworkShort
// bind
if (bind(_socket, (struct sockaddr * )&_server, sizeof(_server)) < 0)
{
puts("\nbind failded\n");
}
puts("\nbind done\n");
// ecoute
listen(_socket, 3);
// Accepte les connexions
puts("Waiting for incoming connections...\n");
c = sizeof(struct sockaddr_in);
while ( (_newSock = accept(_socket, (struct sockaddr * )&_client, (socklen_t * )&c)) )
{
// affiche les infos du _client
printf("Connection from %s:%d accepted\n",inet_ntoa(_client.sin_addr), ntohs(_client.sin_port));
// repond au client
_message = "Hey client! \n.";
write(_newSock, _message, strlen(_message));
}
if (_newSock < 0)
{
puts("accept failed\n");
}
return 0;
}
6. serveur TCP multi-thread
Pour pas abuser sur la longueur de la page, Le détail de comment en arriver à ce code se trouvent sur une autre page
makefile:
TARGET=serveurTCPmulti-Threads
CFLAGS=-Wall -lpthread
all: $(TARGET)
clean:
rm -f $(TARGET)
#include<stdio.h>
#include<stdlib.h>
#include<string.h> // strlen
#include<sys/socket.h>
#include<arpa/inet.h> // inet_addr
#include<unistd.h> // write
#include<pthread.h> // threading
void* connection_handler(void *);
int main(int argc, char **argv)
{
// variables
int _socket, _newSock, c;
int * _newSockPtr;
struct sockaddr_in _server, _client;
char* _message;
// creation de socket
_socket = socket(AF_INET, SOCK_STREAM, 0);
if (_socket == -1)
{
printf("\nCould not create socket\n");
}
// initialisation des membres de l'objet _server
_server.sin_family = AF_INET;
_server.sin_addr.s_addr = INADDR_ANY;
_server.sin_port = htons(atoi(argv[1])); // conversion (char->int)->NetworkShort
// bind
if (bind(_socket, (struct sockaddr * )&_server, sizeof(_server)) < 0)
{
puts("\nbind failded\n");
}
puts("\nbind done\n");
// ecoute
listen(_socket, 3);
// Accepte les connexions
puts("Waiting for incoming connections...\n");
c = sizeof(struct sockaddr_in);
while ( (_newSock = accept(_socket, (struct sockaddr * )&_client, (socklen_t * )&c)) )
{
// affiche les infos du _client
printf("Connection from %s:%d accepted\n",inet_ntoa(_client.sin_addr), ntohs(_client.sin_port));
// repond au client
_message = "Hey client! You will be assigned to a handler... \n.";
write(_newSock, _message, strlen(_message));
// creation d'un nouveau thread
pthread_t sniffer_thread;
_newSockPtr = (int *)malloc(sizeof(int));
*_newSockPtr = _newSock;
if (pthread_create( &sniffer_thread, NULL, connection_handler, (void * ) _newSockPtr) < 0)
{
puts("could not create thread\n");
return 1;
}
perror("Handler assigned\n");
}
if (_newSock < 0)
{
puts("accept failed\n");
}
return 0;
}
void* connection_handler(void *socket_desc)
{
// prend la description du socket
int sock = *(int *)socket_desc;
int read_size;
char *message, client_message[2000];
// envoie quelques messages au client
message = "Greetings! I'm your connection handler\n";
write(sock, message, strlen(message));
message = "Now type somthing and I will repeat it.";
write(sock, message, strlen(message));
// reception de message
while ((read_size = recv(sock, client_message, 2000, 0)) > 0)
{
// renvoie le message au client
write(sock, client_message, strlen(client_message));
}
if (read_size == 0)
{
puts("Client disconnected");
fflush(stdout);
}
else if (read_size == -1)
{
perror("recv failed");
}
// libere la memoire
free(socket_desc);
return 0;
}