Re: [Un peu HS] Truc bizarre avec des sockets Linux

2023-11-24 Par sujet BERTRAND Joël
Bon, trouvé.

Dans tcpServer.c, il faut remonter d'une ligne close(newSocket).

Désolé pour le bruit.



signature.asc
Description: OpenPGP digital signature


Re: [Un peu HS] Truc bizarre avec des sockets Linux

2023-11-23 Par sujet BERTRAND Joël
Désolé, j'ai oublié le code serveur.

JB
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define PORT 

int main(){

	int sockfd, ret;
	 struct sockaddr_in serverAddr;

	int newSocket;
	struct sockaddr_in newAddr;

	socklen_t addr_size;

	char buffer[1024];
	pid_t childpid;

	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if(sockfd < 0){
		printf("[-]Error in connection.\n");
		exit(1);
	}
	printf("[+]Server Socket is created.\n");

	memset(, '\0', sizeof(serverAddr));
	serverAddr.sin_family = AF_INET;
	serverAddr.sin_port = htons(PORT);
	serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");

	ret = bind(sockfd, (struct sockaddr*), sizeof(serverAddr));
	if(ret < 0){
		printf("[-]Error in binding.\n");
		exit(1);
	}
	printf("[+]Bind to port %d\n", );

	if(listen(sockfd, 10) == 0){
		printf("[+]Listening\n");
	}else{
		printf("[-]Error in binding.\n");
	}


	while(1){
		newSocket = accept(sockfd, (struct sockaddr*), _size);
		if(newSocket < 0){
			exit(1);
		}
		printf("accept(%d, %d)\n", sockfd, newSocket);
		printf("Connection accepted from %s:%d\n", inet_ntoa(newAddr.sin_addr), ntohs(newAddr.sin_port));

		if((childpid = fork()) == 0){
			close(sockfd);

			while(1){
recv(newSocket, buffer, 1024, 0);
if(strcmp(buffer, ":exit") == 0){
	printf("Disconnected from %s:%d\n", inet_ntoa(newAddr.sin_addr), ntohs(newAddr.sin_port));
	printf("shutdown: %d\n", shutdown(newSocket, SHUT_RDWR));
	printf("close: %d\n", close(newSocket));
	break;
}else{
	printf("Client: %s\n", buffer);
	send(newSocket, buffer, strlen(buffer), 0);
	bzero(buffer, sizeof(buffer));
}
			}
		}

	}

	close(newSocket);


	return 0;
}


signature.asc
Description: OpenPGP digital signature


Re: [Un peu HS] Truc bizarre avec des sockets Linux

2023-11-23 Par sujet BERTRAND Joël
Je viens de reproduire la chose avec deux bouts de programmes écrits en
C (voir les deux pièces jointes).

hilbert:[~/rpl-test] > ./server
[+]Server Socket is created.
[+]Bind to port 
[+]Listening
accept(3, 4)
Connection accepted from 127.0.0.1:43388
Client: aze
Disconnected from 127.0.0.1:43388
shutdown: 0
close: 0
accept(3, 6)
Connection accepted from 127.0.0.1:49504
Client: aze
Disconnected from 127.0.0.1:49504
shutdown: 0
close: 0

hilbert:[~/rpl-test] > ./client
[+]Client Socket is created.
[+]Connected to Server.
Client: aze
Server: aze
Client: :exit
[-]Disconnected from server.
hilbert:[~/rpl-test] > ./client
[+]Client Socket is created.
[+]Connected to Server.
Client: aze
Server: aze
Client: :exit
[-]Disconnected from server.

Un coup de valgrind montre exactement la même chose lorsque le
processus serveur racine est tué par un SIGINT :

==10415== Process terminating with default action of signal 2 (SIGINT)
==10415==at 0x49A04D0: accept (accept.c:26)
==10415==by 0x109233: main (tcpServer.c:52)
==10415==
==10415== FILE DESCRIPTORS: 7 open (3 std) at exit.
==10415== Open AF_INET socket 6: 127.0.0.1: <-> 127.0.0.1:57256
==10415==at 0x49A04D0: accept (accept.c:26)
==10415==by 0x109233: main (tcpServer.c:52)
==10415==
==10415== Open AF_INET socket 4: 127.0.0.1: <-> 127.0.0.1:46402
==10415==at 0x49A04D0: accept (accept.c:26)
==10415==by 0x109233: main (tcpServer.c:52)
==10415==
==10415== Open AF_INET socket 3: 127.0.0.1: <-> unbound
==10415==at 0x49A0BA7: socket (syscall-template.S:120)
==10415==by 0x109141: main (tcpServer.c:25)
==10415==
==10415== Open file descriptor 5:
==10415==

Les sockets créées par accept() [ici fd=4 et fd=6] restent ouvertes dans
le processus racine du serveur et au bout d'un certain nombre de
connexions (dépendant de la valeur max open files), accept() retourne
une erreur.

À noter : la socket est bien fermée dans le processus créé par fork()
[shutdown + close] et est fermée dans le client.

Merci de vos lumières,

JB
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define PORT 

int main(){

	int clientSocket, ret;
	struct sockaddr_in serverAddr;
	char buffer[1024];

	clientSocket = socket(AF_INET, SOCK_STREAM, 0);
	if(clientSocket < 0){
		printf("[-]Error in connection.\n");
		exit(1);
	}
	printf("[+]Client Socket is created.\n");

	memset(, '\0', sizeof(serverAddr));
	serverAddr.sin_family = AF_INET;
	serverAddr.sin_port = htons(PORT);
	serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");

	ret = connect(clientSocket, (struct sockaddr*), sizeof(serverAddr));
	if(ret < 0){
		printf("[-]Error in connection.\n");
		exit(1);
	}
	printf("[+]Connected to Server.\n");

	while(1){
		printf("Client: \t");
		scanf("%s", [0]);
		send(clientSocket, buffer, strlen(buffer), 0);

		if(strcmp(buffer, ":exit") == 0){
			printf("shutdown: %d\n", shutdown(clientSocket, SHUT_RDWR));
			printf("close: %d\n", close(clientSocket));
			printf("[-]Disconnected from server.\n");
			exit(1);
		}

		if(recv(clientSocket, buffer, 1024, 0) < 0){
			printf("[-]Error in receiving data.\n");
		}else{
			printf("Server: \t%s\n", buffer);
		}
	}

	return 0;
}


signature.asc
Description: OpenPGP digital signature


[Un peu HS] Truc bizarre avec des sockets Linux

2023-11-23 Par sujet BERTRAND Joël
Bonjour à tous,

Je développe le logiciel suivant http://www.rpl2.fr et un utilisateur
du bout du monde vient de me remonter un bug bizarre sur la gestion des
sockets réseau TCP. Je viens de passer la journée dessus et je ne
comprends pas.

Je précise que la fonction a été méchamment testée et qu'il me semble
qu'elle fonctionnait parfaitement lorsqu'elle a été publiée il y a de
cela plusieurs années.

La séquence de commandes C effectuée est la suivante :
- création d'une socket avec socket() et bind() ;
- attente d'une connexion entrante avec accept() ;
- fork() et traitement du client dans le processus fils (y compris la
libération de la socket créée par accept()).

C'est ni plus ni moins que ceci (fonction serve_forever()):
https://gist.github.com/laobubu/d6d0e9beb934b60b2e552c2d03e1409e#file-httpd-c

Dans le langage en question, ça se traduit comme ceci :

SERVEUR
<<
// Création d'une socket TCP écoutant
// sur le port 87 avec un maximum de 16
// sockets clientes.

{
"local" "stream" "flow"
{ "protocol" "ipv4" }
{ "listen" 16 }
{ "port" 87 }
{ "option" "reuse address" }
} open

// Format binaire
{ "length*(*)" } swap format
-> SOCKET
<<
do
   // On attend une connexion
SOCKET wfsock

// Si connexion, on lance un
// traitement dans un processus
// fils.
'CIRCUIT_CONNECTE' detach
// On efface le PID de la pile
drop
// On efface les deux sockets de la pile
drop2
until
false
end
 >>
>>

CIRCUIT_CONNECTE
<<
"Socket connectée" disp
   'SOCKET' sto
   'FERMETURE_SOCKET' atexit

   do
  SOCKET "POLLIN" 2 ->list 1 ->list TIMEOUT_CONNEXION poll
  ...
   until
  ...
   end
>>

FERMETURE_SOCKET
<<
SOCKET close
>>

Si je remplace 'detach' (fork()) par 'spawn' (thread_create()), ça
fonctionne parfaitement bien. Ça peut tourner des heures (j'ai laissé le
processus fonctionner durant plusieurs heures, ce qui correspond à
plusieurs centaines de milliers de connexion sur la socket). Avec
'detach', le programme finit par planter faute de descripteur de socket
disponible (trop de fichiers ouverts).

Je viens de passer le code dans valgrind et je ne comprends pas bien :

Root rayleigh:[~/exemple] > valgrind --track-fds=yes ./connecteur.rpl

+++RPL/2 (R) version 4.1.35 (Jeudi 23/11/2023, 16:42:36 CET)
+++Copyright (C) 1989 à 2022, 2023 BERTRAND Joël
...
socket: 7 <- renvoyée par accept() dans WFSOCK (la socket créée par
socket est la 6)
Socket connectée
close 7 <- fermeture de la socket 7 (par un shutdown() puis close())
close 7 OK
...
socket: 9
Socket connectée
close 9
close 9 OK
==20196== FILE DESCRIPTORS: 7 open (3 std) at exit.
==20196== Open AF_INET socket 7: 127.0.0.1:87 <-> unbound
==20196==at 0x546950F: accept (accept.c:26)
==20196==by 0x5C0661: librpl_instruction_wfsock
(instructions_w1-conv.c:3410)
==20196==by 0x4CC36D: librpl_analyse (analyse-conv.c:1076)
==20196==by 0x4D9C58: librpl_evaluation (evaluation-conv.c:764)
==20196==by 0x5CF16D: librpl_sequenceur_optimise
(optimisation-conv.c:399)
==20196==by 0x5D5A46: librpl_rplinit (rpl-conv.c:5198)
==20196==by 0x466F9D: main (init-conv.c:29)
...

Comment se fait-il que la socket soit toujours ouverte dans le père
(elle a été explicitement fermée dans le processus fils) ? Les
ressources système ne sont pas libérées. Pire, chaque socket cliente
reste ouverte pour le système et le processus parent.

Je résous une partie du problème en rajoutant un close de la socket
cliente après un timeout dans le processus serveur (mais ce n'est pas
satisfaisant).

Je constate aussi que si je ferme dans le processus fils la socket
initiale (celle créée par socket()), accept() râle. Le processus fils
peut donc fermer la socket en attente sur accept() mais s'il ferme la
socket() renvoyée par accept(), il ne la ferme que pour lui-même (et pas
pour le processus père).

La question est donc : suis-je passé à côté de quelque chose ?

J'en reviens donc à ce bout de code :
https://gist.github.com/laobubu/d6d0e9beb934b60b2e552c2d03e1409e#file-httpd-c

Sauf erreur de ma part, dans la fonction serve_forever(), je trouve
bien un accept(), mais jamais de close() sur la socket créée par
accept() (plus exactement, je trouve le close dans le processus détaché
par fork()). Comment ce bout de code peut-il fonctionner sans qu'il ne
finisse par planter par un dépassement du nombre de fichiers ouverts ?

Merci de votre attention,

JB

PS: j'essaie de compiler sur un NetBSD, mais j'ai un problème de
symboles entre ncurses et readline :

ltiple definition of `UP';
../tools/readline-8.2/libreadline.a(terminal.o):(.bss+0xa8): first
defined here
/usr/bin/ld: