简体   繁体   English

Apache 在客户端设置 keep-alive 时主动关闭 tcp 连接

[英]Apache actively close tcp connections when keep-alive is set by the client

I'm trying to do Apache performance benchmarking for my course project.我正在尝试为我的课程项目做 Apache 性能基准测试。 But I meet a strange problem.但我遇到了一个奇怪的问题。 When I use a single client to establish multiple TCP connections (eg 100) to an Apache server and send HTTP 1.1 requests with the Connection: keep-alive header, I suppose the TCP connections can be reused. When I use a single client to establish multiple TCP connections (eg 100) to an Apache server and send HTTP 1.1 requests with the Connection: keep-alive header, I suppose the TCP connections can be reused. But the Apache server will actively terminate TCP connections, even if Connection: Keep-Alive and Keep-Alive: xxx are included in the HTTP response header. But the Apache server will actively terminate TCP connections, even if Connection: Keep-Alive and Keep-Alive: xxx are included in the HTTP response header.

this is my client code (I obfuscate the IP address):这是我的客户端代码(我混淆了 IP 地址):

#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <time.h>
#include <errno.h>
#include <sys/time.h>

#define DEBUG

#define MAX_SOCKETS_NUM 100
#define BUF_LEN 4096

int main() {
    int i;
    struct sockaddr_in server_addr, peer_addr;
    int res, len = sizeof(peer_addr);
    char *req = "GET /20KB HTTP/1.1\r\n"
                "Host: x.x.x.192\r\n"
                "Connection: keep-alive\r\n"
                "Upgrade-Insecure-Requests: 1\r\n"
                "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36\r\n"
                "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9\r\n"
                "Accept-Encoding: gzip, deflate\r\n"
                "Accept-Language: zh-CN,zh;q=0.9\r\n"
                "\r\n";

    char buf[BUF_LEN + 1];
    int sockets[MAX_SOCKETS_NUM];
    int estb_num = 0;
    int estb_map[MAX_SOCKETS_NUM];
    struct timeval goal, now, interval;

    // create sockets
    for (i = 0; i < MAX_SOCKETS_NUM; i++) {
        if ((sockets[i] = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0)) < 0) {
            printf("Socket %d error!\n", i);
            return -1;
        }
    }

    // initialize server_addr
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(80);
    if (inet_pton(AF_INET, "x.x.x.192", &server_addr.sin_addr) <= 0) {
        printf("Invalid address/Address not supported\n");
        return -1;
    }

    // connect to the victim server
    for (i = 0; i < MAX_SOCKETS_NUM; i++) {
        if ((res = connect(sockets[i], (struct sockaddr *)&server_addr, sizeof(server_addr))) == 0) {
#ifdef DEBUG
            printf("Socket %d connected immediately.\n", i);
#endif
            estb_map[i] = 1;
            estb_num++;
        }
        else if (res == -1) {
            if (errno != EINPROGRESS) {
                printf("Error occured when connect() is called on socket %d.\n", i);
                return -1;
            }

#ifdef DEBUG
            printf("Socket %d sends SYN packet but the ACK is not received.\n", i);
#endif
        }
    }

    while (1) {
        if (estb_num == MAX_SOCKETS_NUM)
            break;

        for (i = 0; i < MAX_SOCKETS_NUM; i++) {
            while (1) {
                res = getpeername(sockets[i], (struct sockaddr *)&peer_addr, &len);
                if (res == 0) {
                    estb_num++;
#ifdef DEBUG
                    printf("Socket %d connects successfully.\n", i);
#endif
                    break;
                }
            }
        }
    }
    
    interval = (struct timeval) {
        .tv_sec = 1,
        .tv_usec = 0
    };
    len = strlen(req);
    while (1) {
        gettimeofday(&now, NULL);
        timeradd(&now, &interval, &goal);
#ifdef DEBUG
        printf("%ld.%ld\n", goal.tv_sec, goal.tv_usec);
#endif

        // send requests
        for (i = 0; i < MAX_SOCKETS_NUM; i++) {
            res = send(sockets[i], req, len, 0);
              
            if (res == -1) {
                printf("socket:%ld errno:%ld\n", i, errno);
            }
        }
        
        while (1) {
            for (i = 0; i < MAX_SOCKETS_NUM; i++) {
                res = recv(sockets[i], buf, BUF_LEN, 0);
                if (res == 0) {
                    struct sockaddr_in dbg_addr;
                    int dgb_addr_len = sizeof(struct sockaddr_in);
                    getsockname(sockets[i], &dbg_addr, &dgb_addr_len);

                    printf("socket:%d port:%d\n", i, ntohs(dbg_addr.sin_port));
                    goto end;
                }
                else if (res == -1) {
                    if (errno != EAGAIN)
                        printf("Error occurs when recv is called.\n");
                }
                else {
                    // do nothing because we don't need the response
                }
            }

            gettimeofday(&now, NULL);
            if (timercmp(&now, &goal, >) != 0) {
#ifdef DEBUG
                printf("%d.%d\n", now.tv_sec, now.tv_usec);
#endif
                break;
            }
        }
    }
end:
    return 0;
}

the workflow of the client (with the IP address xxx198) is:客户端(IP 地址为 xxx198)的工作流程是:

  1. establish 100 tcp connections to the server (with the IP address xxx192) using non-blocking socket .使用非阻塞套接字建立到服务器的 100 个 tcp 连接(使用 IP 地址 xxx192)。

  2. send the same requests on the 100 tcp connections在 100 个 tcp 连接上发送相同的请求

  3. invoking non-blocking recv repeatedly on these 100 sockets for 1 second在这 100 个recv上重复调用非阻塞接收 1 秒

  4. goto 2.转到 2。

one execution of the program generates the following output:程序的一次执行生成以下 output:

Socket 0 sends SYN packet but the ACK is not received.
(some lines are omitted)
Socket 99 sends SYN packet but the ACK is not received.
Socket 0 connects successfully.
(some lines are omitted)
Socket 99 connects successfully.
1639450192.343129
1639450192.343155
1639450193.343163
socket:63 port:56804

The output indicates in the second round of requests (the first round is finished because it prints two timestamps, but the second round only prints one), the recv function on the 63th socket with the local port number 56804 returns 0, which means the Apache server actively terminates tcp connections. The output indicates in the second round of requests (the first round is finished because it prints two timestamps, but the second round only prints one), the recv function on the 63th socket with the local port number 56804 returns 0, which means the Apache服务器主动终止 tcp 连接。 And I dumped all the packets on the client using tcpdump, the following figure shows the packet trace of the connection with the local port number 56804:并且我使用tcpdump转储了客户端上的所有数据包,下图显示了与本地端口号56804的连接的数据包跟踪:

在此处输入图像描述

the packet trace shows the same result that the server actively sends tcp FIN packet to the client to terminate the TCP connection.数据包跟踪显示相同的结果,服务器主动向客户端发送 tcp FIN数据包以终止 TCP 连接。 But we can see Connection: Keep-Alive and Keep-Alive: timeout=10m, max=1999 are included in the response header which means the Apache server handles keep-alive correctly.但我们可以看到Connection: Keep-AliveKeep-Alive: timeout=10m, max=1999包含在响应 header 中,这意味着 Apache 服务器正确处理 keep-alive。

The server runs Ubuntu 20.04.3 and Apache 2.4.41.服务器运行 Ubuntu 20.04.3 和 Apache 2.4.41。

I's very confused about why this happens, why will Apache close keep-alive connections?我很困惑为什么会发生这种情况,为什么 Apache 会关闭保持连接? I'd be appreciate if you can help me, thanks!如果您能帮助我,我将不胜感激,谢谢!

From this document :这个文件

A host MAY keep an idle connection open for longer than the time that it indicates, but it SHOULD attempt to retain a connection for at least as long as indicated.主机可以保持空闲连接打开的时间超过它所指示的时间,但它应该尝试保持连接至少与所指示的时间一样长。

Capital letters there are key, and written like this in the document:大写字母有key,在文档中是这样写的:

Your case matches the "SHOULD" part.您的案例与“应该”部分匹配。 Eg keep-alive is a recommendation - but if the server needs those resources (or is configured to have less open connections than the number of your clients) it's free to close them at will.例如,keep-alive 是一个建议 - 但如果服务器需要这些资源(或配置为打开的连接少于您的客户端数量),则可以随意关闭它们。 Your clients will need to deal with this state.您的客户将需要处理此 state。

If the described behavior is dependent on the number of parallel sockets that you open (apart from the timeout), you're most likely running into resource limits on your server - either explicitly configured, or implicit, from default values.如果所描述的行为取决于您打开的并行 sockets 的数量(除了超时),您很可能会遇到服务器上的资源限制——无论是显式配置的还是隐式的,默认值。

Imagine how easy a DDOS attack would be if all that's required was a couple of keep-alive requests to saturate the number of concurrent connections that the server offers.想象一下,如果只需要几个保持活动状态的请求来使服务器提供的并发连接数量饱和,那么 DDOS 攻击将是多么容易。

Also note that timeout is based on different perceptions of time on server (starting with sending the last packet) and client (starting when receiving the last packet)另请注意,超时基于服务器(从发送最后一个数据包开始)和客户端(从接收最后一个数据包开始)对时间的不同感知

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM