Địa chỉ ổ cắm cục bộ TCP đã bị ràng buộc không có sẵn sau bao lâu?


13

Trên Linux (các máy chủ trực tiếp của tôi nằm trên RHEL 5.5 - các liên kết LXR bên dưới là phiên bản kernel trong đó), man 7 ipnói:

Địa chỉ ổ cắm cục bộ TCP đã bị ràng buộc không có sẵn trong một thời gian sau khi đóng, trừ khi cờ SO_REUSEADDR đã được đặt.

Tôi không sử dụng SO_REUSEADDR. "Một thời gian" là bao lâu? Làm thế nào tôi có thể tìm ra nó dài bao nhiêu và làm thế nào tôi có thể thay đổi nó?

Tôi đã loay hoay xung quanh vấn đề này và đã tìm thấy một vài thông tin, không ai trong số đó thực sự giải thích điều này từ quan điểm của một lập trình viên ứng dụng. Để dí dỏm:

  • TCP_TIMEWAIT_LEN trong net/tcp.h"thời gian chờ đợi để hủy trạng thái TIME-WAIT" và được cố định ở "khoảng 60 giây"
  • / Proc / sys / net / ipv4 / tcp_fin_timeout là "Thời gian giữ ổ cắm ở trạng thái FIN-WAIT-2, nếu nó được đóng bên cạnh chúng tôi" và "Giá trị mặc định là 60 giây"

Trường hợp tôi vấp ngã là thu hẹp khoảng cách giữa mô hình vòng đời TCP của hạt nhân và mô hình cổng của lập trình viên không khả dụng, nghĩa là, để hiểu các trạng thái này liên quan đến "một thời gian" như thế nào.


@Caleb: Liên quan đến các thẻ, liên kết là một cuộc gọi hệ thống quá! Hãy thử man 2 bindnếu bạn không tin tôi. Phải thừa nhận rằng, đây có lẽ không phải là điều đầu tiên mọi người nghĩ đến khi ai đó nói "ràng buộc", như vậy là đủ công bằng.
Tom Anderson

Tôi đã nhận thức rõ về việc sử dụng thay thế bind, nhưng thẻ ở đây được áp dụng cụ thể cho máy chủ DNS. Chúng tôi không có thẻ cho mọi cuộc gọi hệ thống có thể.
Caleb

Câu trả lời:


14

Tôi tin rằng ý tưởng về ổ cắm không có sẵn cho một chương trình là cho phép mọi phân đoạn dữ liệu TCP vẫn đang trong quá trình chuyển đến và bị loại bỏ bởi kernel. Nghĩa là, ứng dụng có thể gọi close(2)trên một ổ cắm, nhưng việc định tuyến trễ hoặc rủi ro để kiểm soát các gói hoặc những gì bạn có thể cho phép phía bên kia của kết nối TCP gửi dữ liệu trong một thời gian. Ứng dụng đã chỉ ra rằng nó không còn muốn xử lý các phân đoạn dữ liệu TCP, vì vậy kernel chỉ nên loại bỏ chúng khi chúng đi vào.

Tôi đã hack một chương trình nhỏ trong C mà bạn có thể biên dịch và sử dụng để xem thời gian chờ là bao lâu:

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

Tôi đã thử chương trình này trên 3 máy khác nhau và tôi nhận được thời gian thay đổi, trong khoảng từ 55 đến 59 giây, khi hạt nhân từ chối cho phép người dùng không root để mở lại ổ cắm. Tôi đã biên dịch mã ở trên thành một "cái mở" có thể thực thi được và chạy nó như thế này:

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

Tôi đã mở một cửa sổ khác và làm điều này:

telnet otherhost 7896

Điều đó khiến cho phiên bản đầu tiên của "công cụ mở" chấp nhận kết nối, sau đó đóng lại. Ví dụ thứ hai của "cái mở" cố gắng đến bind(2)cổng TCP 7896 mỗi giây. "Người mở" báo cáo độ trễ 55 đến 59 giây.

Googling xung quanh, tôi thấy rằng mọi người khuyên bạn nên làm điều này:

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

để giảm khoảng thời gian đó. Nó không làm việc cho tôi. Trong số 4 máy linux tôi có quyền truy cập, hai máy có 30 và hai máy có 60. Tôi cũng đặt giá trị đó ở mức 10. Không khác biệt với chương trình "mở".

Làm điều này:

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

đã thay đổi mọi thứ. "Cái mở" thứ hai chỉ mất khoảng 3 giây để có được ổ cắm mới.


3
Tôi hiểu (đại khái) mục đích của thời kỳ không có sẵn là gì. Những gì tôi muốn biết là chính xác khoảng thời gian đó trên Linux là bao lâu và nó có thể thay đổi như thế nào. Vấn đề với một số từ trang Wikipedia về TCP là nó nhất thiết phải là một giá trị tổng quát và không phải là thứ gì đó hoàn toàn đúng với nền tảng cụ thể của tôi.
Tom Anderson

suy đoán của bạn thật thú vị! chỉ gắn cờ chúng với tiêu đề thay vì xóa chúng, nó đưa ra các cách để tìm kiếm lý do tại sao!
Philippe Gachoud
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.