Client sends RST to recieved packets after shutdown(SHUT_WR)

时间:2019-04-16 22:48:54

标签: c++ linux sockets tcp network-programming

After closing the writing direction of a connection of a socket using shutdown() all subsequently received data causes an RST packet to be sent and read() returns a size of 0 indicating EOF for the reading direction. Why is this the case, even though the reading direction wasn't closed?

Using wireshark I checked the sent packets:

No. Time     Source        Destination   Protocol Length Info
1   0.000000 192.168.0.175 3.85.154.144  TCP      78     55318 → 80 [SYN] Seq=0 Win=65535 Len=0 MSS=1460 WS=32
2   0.114608 3.85.154.144  192.168.0.175 TCP      74     80 → 55318 [SYN, ACK] Seq=0 Ack=1 Win=26847 Len=0 MSS=1460 WS=256
3   0.114706 192.168.0.175 3.85.154.144  TCP      66     55318 → 80 [ACK] Seq=1 Ack=1 Win=131744 Len=0
4   0.115371 192.168.0.175 3.85.154.144  HTTP     112    GET /bytes/512 HTTP/1.1 
5   0.115401 192.168.0.175 3.85.154.144  TCP      66     55318 → 80 [FIN, ACK] Seq=47 Ack=1 Win=131744 Len=0
6   0.222652 3.85.154.144  192.168.0.175 TCP      66     80 → 55318 [ACK] Seq=1 Ack=47 Win=26880 Len=0
7   0.224444 3.85.154.144  192.168.0.175 HTTP     801    HTTP/1.1 200 OK  (application/octet-stream)
8   0.224543 192.168.0.175 3.85.154.144  TCP      54     55318 → 80 [RST] Seq=48 Win=0 Len=0
9   0.226056 3.85.154.144  192.168.0.175 TCP      66     80 → 55318 [FIN, ACK] Seq=736 Ack=48 Win=26880 Len=0
10  0.226100 192.168.0.175 3.85.154.144  TCP      54     55318 → 80 [RST] Seq=48 Win=0 Len=0

After the [FIN, ACK] is sent, all received data is responded to with RSTs. It seems like the local side is thinking the connection is fully closed even though a FIN was sent (indicating the end of written data) instead of an RST (indicating the end of the connection). The remote side expects to be able to send data, but can't. Monitoring the socket status with netstat reveals that the connection is instantly fully closed after shutdown(sockfd, SHUT_WR) is called.

Here's a MWE in C++. It assumes all non-relevant functions succeed and aborts otherwise. The return codes of all functions are checked for errors to make sure they aren't at fault for the results.

#include <arpa/inet.h>
#include <cerrno>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>

#define RUN_OR_ABORT(fun) {\
    int RETVAL = fun;\
    if (RETVAL == -1)\
    {\
        perror(#fun);\
        abort();\
    }\
}

int main() {
    // Establish connection to httpbin.org:80
    int sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (sockfd == -1) exit(EXIT_FAILURE);
    sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(80);
    // "httpbin.org" resolves to 3.85.154.144 or 52.71.234.219
    int retcode = inet_pton(addr.sin_family, "3.85.154.144", &addr.sin_addr);
    if (retcode != 1) exit(EXIT_FAILURE);
    RUN_OR_ABORT(connect(sockfd, (sockaddr*)&addr, sizeof(addr)));

    // Request 512 random bytes
    const char* msg = "GET /bytes/512 HTTP/1.1\r\nHost: httpbin.org\r\n\r\n";
    ssize_t write_len = write(sockfd, msg, strlen(msg));
    if (write_len != strlen(msg)) exit(EXIT_FAILURE);
    RUN_OR_ABORT(shutdown(sockfd, SHUT_WR));

    // Prepare buffer for recieving data
    size_t buf_size = 1024, read_offset = 0;
    char* buf = new char[buf_size];

    // Read as long as there is data available
    while (true) {
        ssize_t read_len = read(sockfd, buf + read_offset, buf_size - read_offset);
        if (read_len == -1 and (errno & (EAGAIN | EINTR)))
            continue;
        else if (read_len == -1) {
            perror("recv()");
            exit(EXIT_FAILURE);
        }
        read_offset += read_len;
        if (read_len == 0) {
            // EOF?
            printf("%lu bytes have been read\n", read_offset);
            break;
        }
        if (read_offset >= buf_size) {
            fprintf(stderr, "Unexpectedly large response\n");
            exit(EXIT_FAILURE);
        }
    }
}

I would have expected the read() call to return a non-zero size and the socket to remain open for reading after closing the writing end with shutdown(sockfd, SHUT_WR). The expected output of the MWE would be:

740 bytes have been read

(or similar, but the number of read bytes should be bigger than 512 bytes). The actual output was:

0 bytes have been read

1 个答案:

答案 0 :(得分:-1)

问题是(行为异常)透明的HTTP代理。 Avast的Web Shield会在发送或接收数据包之前对其进行拦截,并检查它们是否存在恶意软件。 Avast为此使用了单独的内部插座。发送第一个FIN时,Avast(错误地)关闭其内部套接字。这被传递到客户端的套接字,这就是为什么它以后不会出现在netstat中的原因。然后,服务器的数据将被拒绝,因为负责处理传入数据的Avast套接字已不再打开。 Avast的关闭第一个FIN数据包(不告知服务器;在接收数据之前没有RST数据包)上的连接的策略行得通,因为几乎所有HTTP客户端从不调用shutdown(sockfd, SHUT_WR)且仅调用close()连接,当没有更多数据要读取时。

相关问题