Je crois que l'idée que le socket n'est pas disponible pour un programme est de permettre à tous les segments de données TCP encore en transit d'arriver et d'être rejetés par le noyau. Autrement dit, il est possible pour une application d'appeler close(2)
sur un socket, mais les retards ou les incidents de routage pour contrôler les paquets ou ce que vous avez peuvent permettre à l'autre côté d'une connexion TCP d'envoyer des données pendant un certain temps. L'application a indiqué qu'elle ne souhaitait plus traiter les segments de données TCP, le noyau devrait donc simplement les supprimer à mesure qu'ils entrent.
J'ai piraté un petit programme en C que vous pouvez compiler et utiliser pour voir combien de temps le timeout est:
#include <stdio.h> /* fprintf() */
#include <string.h> /* strerror() */
#include <errno.h> /* errno */
#include <stdlib.h> /* strtol() */
#include <signal.h> /* signal() */
#include <sys/time.h> /* struct timeval */
#include <unistd.h> /* read(), write(), close(), gettimeofday() */
#include <sys/types.h> /* socket() */
#include <sys/socket.h> /* socket-related stuff */
#include <netinet/in.h>
#include <arpa/inet.h> /* inet_ntoa() */
float elapsed_time(struct timeval before, struct timeval after);
int
main(int ac, char **av)
{
int opt;
int listen_fd = -1;
unsigned short port = 0;
struct sockaddr_in serv_addr;
struct timeval before_bind;
struct timeval after_bind;
while (-1 != (opt = getopt(ac, av, "p:"))) {
switch (opt) {
case 'p':
port = (unsigned short)atoi(optarg);
break;
}
}
if (0 == port) {
fprintf(stderr, "Need a port to listen on\n");
return 2;
}
if (0 > (listen_fd = socket(AF_INET, SOCK_STREAM, 0))) {
fprintf(stderr, "Opening socket: %s\n", strerror(errno));
return 1;
}
memset(&serv_addr, '\0', sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port = htons(port);
gettimeofday(&before_bind, NULL);
while (0 > bind(listen_fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr))) {
fprintf(stderr, "binding socket to port %d: %s\n",
ntohs(serv_addr.sin_port),
strerror(errno));
sleep(1);
}
gettimeofday(&after_bind, NULL);
printf("bind took %.5f seconds\n", elapsed_time(before_bind, after_bind));
printf("# Listening on port %d\n", ntohs(serv_addr.sin_port));
if (0 > listen(listen_fd, 100)) {
fprintf(stderr, "listen() on fd %d: %s\n",
listen_fd,
strerror(errno));
return 1;
}
{
struct sockaddr_in cli_addr;
struct timeval before;
int newfd;
socklen_t clilen;
clilen = sizeof(cli_addr);
if (0 > (newfd = accept(listen_fd, (struct sockaddr *)&cli_addr, &clilen))) {
fprintf(stderr, "accept() on fd %d: %s\n", listen_fd, strerror(errno));
exit(2);
}
gettimeofday(&before, NULL);
printf("At %ld.%06ld\tconnected to: %s\n",
before.tv_sec, before.tv_usec,
inet_ntoa(cli_addr.sin_addr)
);
fflush(stdout);
while (close(newfd) == EINTR) ;
}
if (0 > close(listen_fd))
fprintf(stderr, "Closing socket: %s\n", strerror(errno));
return 0;
}
float
elapsed_time(struct timeval before, struct timeval after)
{
float r = 0.0;
if (before.tv_usec > after.tv_usec) {
after.tv_usec += 1000000;
--after.tv_sec;
}
r = (float)(after.tv_sec - before.tv_sec)
+ (1.0E-6)*(float)(after.tv_usec - before.tv_usec);
return r;
}
J'ai essayé ce programme sur 3 machines différentes, et j'obtiens un temps variable, entre 55 et 59 secondes, lorsque le noyau refuse d'autoriser un utilisateur non root à rouvrir un socket. J'ai compilé le code ci-dessus dans un exécutable nommé "opener" et l'ai exécuté comme ceci:
./opener -p 7896; ./opener -p 7896
J'ai ouvert une autre fenêtre et j'ai fait ceci:
telnet otherhost 7896
Cela oblige la première instance de "opener" à accepter une connexion, puis à la fermer. La seconde instance de "opener" essaie de bind(2)
se connecter au port TCP 7896 chaque seconde. "ouvreur" signale 55 à 59 secondes de retard.
Googler autour, je trouve que les gens recommandent de faire ceci:
echo 30 > /proc/sys/net/ipv4/tcp_fin_timeout
pour réduire cet intervalle. Ça n'a pas marché pour moi. Sur les 4 machines Linux auxquelles j'avais accès, deux en avaient 30 et deux en avaient 60. J'ai également réglé cette valeur à 10. Aucune différence avec le programme "opener".
Ce faisant:
echo 1 > /proc/sys/net/ipv4/tcp_tw_recycle
a changé les choses. Le deuxième "ouvreur" n'a pris que 3 secondes environ pour obtenir sa nouvelle prise.
man 2 bind
si vous ne me croyez pas. Certes, ce n'est probablement pas la première chose à laquelle les gens d'Unix pensent quand quelqu'un dit "lier", donc assez juste.