Berapa lama alamat soket lokal TCP yang telah terikat tidak tersedia setelah ditutup?

13

Di Linux (server langsung saya ada di RHEL 5.5 - tautan LXR di bawah ini ke versi kernel di dalamnya), man 7 ipmengatakan:

Alamat soket lokal TCP yang telah diikat tidak tersedia untuk beberapa waktu setelah ditutup, kecuali jika flag SO_REUSEADDR telah ditetapkan.

Saya tidak menggunakan SO_REUSEADDR. Berapa lama "beberapa waktu"? Bagaimana saya bisa mengetahui berapa lama, dan bagaimana saya bisa mengubahnya?

Saya sudah mencari-cari di sekitar ini, dan telah menemukan beberapa informasi, tidak ada yang benar-benar menjelaskan hal ini dari perspektif pemrogram aplikasi. Yakni:

  • TCP_TIMEWAIT_LEN di net/tcp.hadalah "berapa lama menunggu untuk menghancurkan status TIME-WAIT", dan diperbaiki pada "sekitar 60 detik"
  • / proc / sys / net / ipv4 / tcp_fin_timeout adalah "Saatnya memegang soket dalam keadaan FIN-WAIT-2, jika ditutup oleh pihak kami", dan "Nilai default adalah 60sec"

Di mana saya tersandung adalah dalam menjembatani kesenjangan antara model kernel dari siklus hidup TCP, dan model pemrogram port tidak tersedia, yaitu, dalam memahami bagaimana keadaan ini berhubungan dengan "beberapa waktu".

Tom Anderson
sumber
@ Caleb: Mengenai tag, bind adalah panggilan sistem juga! Coba man 2 bindjika Anda tidak percaya padaku. Memang, itu mungkin bukan hal pertama yang orang pikirkan ketika seseorang mengatakan "bind", cukup adil.
Tom Anderson
Saya menyadari penggunaan alternatif bind, tetapi tag di sini secara khusus diterapkan ke server DNS. Kami tidak memiliki tag untuk setiap panggilan sistem yang mungkin.
Caleb

Jawaban:

14

Saya percaya bahwa ide soket tidak tersedia untuk suatu program adalah untuk memungkinkan segmen data TCP yang masih dalam perjalanan untuk tiba, dan dibuang oleh kernel. Yaitu, mungkin saja suatu aplikasi memanggil close(2)soket, tetapi merutekan keterlambatan atau kecelakaan untuk mengontrol paket atau apa saja yang Anda bisa izinkan sisi lain koneksi TCP mengirim data untuk sementara waktu. Aplikasi telah mengindikasikan bahwa ia tidak lagi ingin berurusan dengan segmen data TCP, jadi kernel harus membuangnya begitu mereka masuk.

Saya meretas sedikit program di C yang dapat Anda kompilasi dan gunakan untuk melihat berapa lama waktu tunggu:

#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;
}

Saya mencoba program ini pada 3 mesin yang berbeda, dan saya mendapatkan waktu variabel, antara 55 dan 59 detik, ketika kernel menolak untuk mengizinkan pengguna non-root untuk membuka kembali soket. Saya mengkompilasi kode di atas ke executable bernama "pembuka", dan menjalankannya seperti ini:

./opener -p 7896; ./opener -p 7896

Saya membuka jendela lain dan melakukan ini:

telnet otherhost 7896

Itu menyebabkan instance pertama dari "pembuka" menerima koneksi, lalu menutupnya. Contoh kedua dari "pembuka" mencoba untuk bind(2)ke port TCP 7896 setiap detik. "pembuka" melaporkan penundaan 55 hingga 59 detik.

Googling sekitar, saya menemukan bahwa orang merekomendasikan melakukan ini:

echo 30 > /proc/sys/net/ipv4/tcp_fin_timeout

untuk mengurangi interval itu. Itu tidak berhasil untuk saya. Dari 4 mesin linux yang saya akses, dua memiliki 30 dan dua memiliki 60. Saya juga menetapkan nilai serendah 10. Tidak ada perbedaan dengan program "pembuka".

Melakukan ini:

echo 1 > /proc/sys/net/ipv4/tcp_tw_recycle

memang mengubah hal-hal. "Pembuka" kedua hanya membutuhkan waktu sekitar 3 detik untuk mendapatkan soket baru.

Bruce Ediger
sumber
3
Saya mengerti (secara kasar) apa tujuan dari periode ketidaktersediaan. Yang ingin saya ketahui adalah berapa lama periode itu ada di Linux, dan bagaimana bisa diubah. Masalah dengan nomor dari halaman Wikipedia tentang TCP adalah bahwa itu selalu merupakan nilai umum, dan bukan sesuatu yang pasti benar untuk platform spesifik saya.
Tom Anderson
spekulasi Anda menarik! cukup tandai mereka seperti itu dengan judul alih-alih menghapusnya, itu memberi cara untuk mencari alasan mengapa!
Philippe Gachoud