Analysis of TCP Reset Message Segments and RST Common Scenarios

Keywords: PHP socket network Linux

RST represents a connection reset used to close connections that no longer need to exist.Normally it means an abnormal closing of the connection, distinguishing it from a normal closing of the connection after four breakups.

The three conditions for generating RST are:

  1. The SYN destined for a port arrives, but there is no server listening on that port;
  2. TCP wants to cancel an existing connection;
  3. TCP receives a section on a connection that does not exist at all.

The following scenarios generate RST s to illustrate the purpose of resetting message segments.

1. Connection requests for ports that do not exist

The client initiates a connection request SYN to a port on the server side, but the destination server host does not exist the port. The client responds to the RST and interrupts the connection request.

The following is an analysis by program and package capture.The program source code is as follows:

use std::io::prelude::*;
use std::net::TcpStream;
use std::thread;

fn main() {
    let mut stream = TcpStream::connect("192.168.2.229:33333").unwrap();
    let n = stream.write(&[1,2,3,4,5,6,7,8,9]).unwrap();
    println!("send {} bytes to remote node, waiting for end.", n);

    loop{
        thread::sleep_ms(1000);
    }
}

The above program initiates a TCP connection to the destination host 192.168.2.229, which does not start the listening service on port 33333.So when the local host initiates a TCP connection to the destination host, it receives the RST from the destination host and disconnects.(Not all replies to RST, of course, and some hosts may not reply).Snap packs as follows:

Local host sends TCP connection SYN to destination host:

Destination host replies ACK, RST to local host:

2. Terminate a connection

The normal way to terminate a connection is for the communicating party to send a FIN.This method is also known as ordered release.Because FINs are sent after all queued data has been sent before, there is usually no loss of data.However, at any time, we can terminate a connection by sending a reset message segment RST instead of FIN.This is also known as termination of release.

Terminating a connection provides two main features for an application:

  • Any queued data will be discarded and a reset message segment will be sent immediately.
  • The receiver resetting the segment will explain that the other end of the communication terminated instead of shutting down normally.The API must provide a way to implement the termination behavior described above in place of a normal shutdown operation.

The socket API accomplishes this by setting the value of the socket option SO_LINGER to 0.

/* Structure used to manipulate the SO_LINGER option.  */
struct linger
  {
    int l_onoff;        /* Nonzero to linger on close.  */
    int l_linger;       /* Time to linger.  */
  };

The different values of SO_LINGER mean the following:

  1. The l_onoff is 0, the l_linger value is ignored, the kernel default, the close() call is immediately returned to the caller, and the TCP module is responsible for attempting to send residual cache data.
  2. If l_onoff is non-zero and l_linger is 0, the connection terminates immediately. TCP discards the data left in the send buffer and sends RST to the other party instead of sending FIN. This avoids the TIME_WAIT state and the other party will receive the Connection reset by peer error when read ing ().
  3. l_onoff is non-zero and l_linger is greater than zero: If the TCP module successfully sends the remaining buffer data within the l_linger time range, it shuts down normally. If it times out, it sends RST to the other party, discarding the remaining data in the sending buffer.

Client Code 1, Service Code as follows:

/* echo server with poll */
#include <poll.h>
#include<stdio.h>
#include<stdlib.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<unistd.h>
#include<string.h>
#include<fcntl.h>
#include<errno.h>
#include<pthread.h>

#define OPEN_MAX 1024
#define LISTEN_PORT 33333
#define MAX_BUF 1024

int set_linger(int sock, int l_onoff, int l_linger);
int handle_conn(struct pollfd *nfds, char* buf);
void run();

int main(int _argc, char* _argv[]) {
    run();

    return 0;
}

void run() {
    // bind socket
    char str[INET_ADDRSTRLEN];
    struct sockaddr_in seraddr, cliaddr;
    socklen_t cliaddr_len = sizeof(cliaddr);
    int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
    bzero(&seraddr, sizeof(seraddr));
    seraddr.sin_family = AF_INET;
    seraddr.sin_addr.s_addr = htonl(INADDR_ANY);
    seraddr.sin_port = htons(LISTEN_PORT);

    int opt = 1;
    setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    if (-1 == bind(listen_sock, (struct sockaddr*)&seraddr, sizeof(seraddr))) {
        perror("bind server addr failure.");
        exit(EXIT_FAILURE);
    }
    listen(listen_sock, 5);

    int ret, i;
    struct pollfd nfds[OPEN_MAX];
    for (i=0;i<OPEN_MAX;++i){
        nfds[i].fd = -1;
    }

    nfds[0].fd = listen_sock;
    nfds[0].events = POLLIN;

    char* buf = (char*)malloc(MAX_BUF);   
    while (1) {
        ret = poll(nfds, OPEN_MAX, NULL);
        if (-1 == ret) {
            perror("poll failure.");
            exit(EXIT_FAILURE);
        }

        /* An event on one of the fds has occurred. */
        if (nfds[0].revents & POLLIN) {
            int conn_sock = accept(listen_sock, (struct sockaddr *)&cliaddr, &cliaddr_len);
            if (-1 == conn_sock) {
                perror("accept failure.");
                exit(EXIT_FAILURE);
            }
            printf("accept from %s:%d\n", inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), ntohs(cliaddr.sin_port));

            set_linger(conn_sock, 1, 0);    //Set SO_LINGER option value to 0
            for (int k=0;k<OPEN_MAX;++k){
                if (nfds[k].fd < 0){
                    nfds[k].fd = conn_sock;
                    nfds[k].events = POLLIN;
                    break;
                }
                if (k == OPEN_MAX-1){
                    perror("too many clients, nfds size is not enough.");
                    exit(EXIT_FAILURE);
                }
            }
        }

        handle_conn(nfds, buf);
    }

    close(listen_sock);
}

int handle_conn(struct pollfd *nfds, char* buf) {
    int n = 0;
    for (int i=1;i<OPEN_MAX;++i) {
        if (nfds[i].fd<0) {
            continue;
        }

        if (nfds[i].revents & POLLIN) {
            bzero(buf, MAX_BUF);
            n = read(nfds[i].fd, buf, MAX_BUF);
            if (0 == n) {
                close(nfds[i].fd);
                nfds[i].fd = -1;
                continue;
            } 
            if (n>0){
                printf("recv from client: %s\n", buf);
                nfds[i].events = POLLIN;

                close(nfds[i].fd);  //Actively close connection after receiving data for RST testing          
            } else {
                perror("read failure.");
                exit(EXIT_FAILURE);
            }
        } else if (nfds[i].revents & POLLOUT) {
            printf("write data to client: %s\n", buf);
            write(nfds[i].fd, buf, sizeof(buf));
            bzero(buf, MAX_BUF);          

            nfds[i].events = POLLIN;
        }
    }

    return 0;
}

int set_linger(int sock, int l_onoff, int l_linger) {
    struct linger so_linger;
    so_linger.l_onoff = l_onoff;
    so_linger.l_linger = l_linger;
    int r = setsockopt(sock, SOL_SOCKET, SO_LINGER, &so_linger, sizeof(so_linger));

    return r;
}

The results are as follows:

After three handshakes, the client sends 5 bytes of data to the service. After receiving 5 bytes of data to the client ACK, the server indicates that it wants to disconnect the connection. When SO_LINGER option value is set to 0, close(), RST is sent directly to the other party instead of FIN, and the connection terminates immediately without TIME_W.AIT status, TCP will discard data left in the send buffer and receive an error from Connection reset by peer when the other party read s ().

3. Half open connection

If one end of the communication closes or terminates the connection without informing the other end, the TCP connection is considered to be semi-open.

For example, when the server host is restarted after being disconnected from the power supply (disconnect the network cable before disconnecting the power supply, and then connect after restarting), the client is half-open.When the client sends data to the server again, the server knows nothing about the connection and interrupts the connection by replying to a reset message segment RST.

Or if the program turns on TCP lifesaving, send RST to disconnect the connection when the other host is detected to be unreachable.Refer to another of my blogs for more information TCP Lifetime.

A TCP connection that does not receive or receive data for a long time causes TCP to send a live probe message to maintain the connection or to detect whether the connection exists.

You can see that if you think the connection does not exist, an RST is sent to disconnect the connection.

4. Close connection in advance

TCP applications receive data from TCP data received from the operating system. If the data reaches the operating system but I do not want to continue receiving data from my application data, RST disconnects.

Server side code:

/* echo server with poll */
#include <poll.h>
#include<stdio.h>
#include<stdlib.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<unistd.h>
#include<string.h>
#include<fcntl.h>
#include<errno.h>
#include<pthread.h>

#define OPEN_MAX 1024
#define LISTEN_PORT 33333
#define MAX_BUF 1024

#define RST_TEST 1

int handle_conn(struct pollfd *nfds, char* buf);
void run();

int main(int _argc, char* _argv[]) {
    run();

    return 0;
}

void run() {
    // bind socket
    char str[INET_ADDRSTRLEN];
    struct sockaddr_in seraddr, cliaddr;
    socklen_t cliaddr_len = sizeof(cliaddr);
    int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
    bzero(&seraddr, sizeof(seraddr));
    seraddr.sin_family = AF_INET;
    seraddr.sin_addr.s_addr = htonl(INADDR_ANY);
    seraddr.sin_port = htons(LISTEN_PORT);

    int opt = 1;
    setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    if (-1 == bind(listen_sock, (struct sockaddr*)&seraddr, sizeof(seraddr))) {
        perror("bind server addr failure.");
        exit(EXIT_FAILURE);
    }
    listen(listen_sock, 5);

    int ret, i;
    struct pollfd nfds[OPEN_MAX];
    for (i=0;i<OPEN_MAX;++i){
        nfds[i].fd = -1;
    }

    nfds[0].fd = listen_sock;
    nfds[0].events = POLLIN;

    char* buf = (char*)malloc(MAX_BUF);   
    while (1) {
        ret = poll(nfds, OPEN_MAX, NULL);
        if (-1 == ret) {
            perror("poll failure.");
            exit(EXIT_FAILURE);
        }

        /* An event on one of the fds has occurred. */
        if (nfds[0].revents & POLLIN) {
            int conn_sock = accept(listen_sock, (struct sockaddr *)&cliaddr, &cliaddr_len);
            if (-1 == conn_sock) {
                perror("accept failure.");
                exit(EXIT_FAILURE);
            }
            printf("accept from %s:%d\n", inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), ntohs(cliaddr.sin_port));

            for (int k=0;k<OPEN_MAX;++k){
                if (nfds[k].fd < 0){
                    nfds[k].fd = conn_sock;
                    nfds[k].events = POLLIN;
                    break;
                }
                if (k == OPEN_MAX-1){
                    perror("too many clients, nfds size is not enough.");
                    exit(EXIT_FAILURE);
                }
            }
        }

        handle_conn(nfds, buf);
    }

    close(listen_sock);
}

int handle_conn(struct pollfd *nfds, char* buf) {
    int n = 0;
    for (int i=1;i<OPEN_MAX;++i) {
        if (nfds[i].fd<0) {
            continue;
        }

        if (nfds[i].revents & POLLIN) {
            bzero(buf, MAX_BUF);
#if RST_TEST == 0
            n = read(nfds[i].fd, buf, MAX_BUF);
#else
            n = read(nfds[i].fd, buf, 5);      //Actively close the connection when only part of the data is received for RST testing         
#endif
            if (0 == n) {
                close(nfds[i].fd);
                nfds[i].fd = -1;
                continue;
            } 
            if (n>0){
                printf("recv from client: %s\n", buf);
                nfds[i].events = POLLOUT;
#if RST_TEST != 0  
                close(nfds[i].fd);  //Actively close the connection when only part of the data is received for RST testing
#endif            
            } else {
                perror("read failure.");
                exit(EXIT_FAILURE);
            }
        } else if (nfds[i].revents & POLLOUT) {
            printf("write data to client: %s\n", buf);
            write(nfds[i].fd, buf, sizeof(buf));
            bzero(buf, MAX_BUF);          

            nfds[i].events = POLLIN;
        }
    }

    return 0;
}

After the client initiates the connection and sends more than 5 bytes of data, the server sends RST to the client to disconnect the connection because the server no longer receives data after only 5 bytes of data (at this time, the server operating system has received 10 bytes of data, but the upper read system call only receives 5 bytes).The results are as follows:

The client sends 10 bytes of data after three handshakes, and the server sends RST to disconnect the connection in response to the client ACK receiving data.

5. Receive data on a closed TCP connection

If a closed TCP connection receives data again, which is obviously an exception, then RST should disconnect the connection.

The other code on the server side is the same as the last one. Replace it with the following function

int handle_conn(struct pollfd *nfds, char* buf) {
    int n = 0;
    for (int i=1;i<OPEN_MAX;++i) {
        if (nfds[i].fd<0) {
            continue;
        }

        if (nfds[i].revents & POLLIN) {
            bzero(buf, MAX_BUF);
            n = read(nfds[i].fd, buf, MAX_BUF);
            if (0 == n) {
                close(nfds[i].fd);
                nfds[i].fd = -1;
                continue;
            } 
            if (n>0){
                printf("recv from client: %s\n", buf);
                nfds[i].events = POLLOUT;

                close(nfds[i].fd);  //Actively close connection after receiving data for RST testing          
            } else {
                perror("read failure.");
                exit(EXIT_FAILURE);
            }
        } else if (nfds[i].revents & POLLOUT) {
            printf("write data to client: %s\n", buf);
            write(nfds[i].fd, buf, sizeof(buf));
            bzero(buf, MAX_BUF);          

            nfds[i].events = POLLIN;
        }
    }

    return 0;
}

The client code is the same as the previous one, except for the following functions, which can be replaced:

void client_handle(int sock) {
    char sendbuf[MAXLEN], recvbuf[MAXLEN];
    bzero(sendbuf, MAXLEN);
    bzero(recvbuf, MAXLEN);
    int n = 0;

    while (1) {
        if (NULL == fgets(sendbuf, MAXLEN, stdin)) {
            break;
        }
        // Exit by `#`
        if ('#' == sendbuf[0]) {
            break;
        }
        struct timeval start, end;
        gettimeofday(&start, NULL);
        write(sock, sendbuf, strlen(sendbuf));
        sleep(2);
        write(sock, sendbuf, strlen(sendbuf));      //Here is the code to test RST
        sleep(60);
        n = read(sock, recvbuf, MAXLEN);
        if (0 == n) {
            break;
        }
        write(STDOUT_FILENO, recvbuf, n);
        gettimeofday(&end, NULL);
        printf("time diff=%ld microseconds\n", ((end.tv_sec * 1000000 + end.tv_usec)- (start.tv_sec * 1000000 + start.tv_usec)));
    }

    close(sock);
}

Snap packs as follows:

The first three handshakes; the client sends 5 bytes of data to the server, and the server receives 5 bytes of data to reply to the ACK; the client sends FIN to the client, closes the connection, but at this time the client still has data to send, does not initiate FIN to the server, only two waves are made; then the client sends 5 more to the serverByte data, but at this time the server has called close() to close the connection. It is an exception to receive data from the connection again, replying that RST disconnected the connection.

6. Appendix

Client code for testing

#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<netdb.h>
#include <time.h>
#include <sys/time.h>
#include<stdlib.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>

#define SERVER_PORT 33333
#define MAXLEN 65535

void client_handle(int sock);


int main(int argc, char* argv[]) {
    for (int i = 1; i < argc; ++i) {
        printf("input args %d: %s\n", i, argv[i]);
    }
    struct sockaddr_in seraddr;
    int server_port = SERVER_PORT;
    if (2 == argc) {
        server_port = atoi(argv[1]);
    }

    int sock = socket(AF_INET, SOCK_STREAM, 0);
    bzero(&seraddr, sizeof(seraddr));
    seraddr.sin_family = AF_INET;
    inet_pton(AF_INET, "127.0.0.1", &seraddr.sin_addr);
    seraddr.sin_port = htons(server_port);

    if (-1 == connect(sock, (struct sockaddr *)&seraddr, sizeof(seraddr))) {
        perror("connect failure");
        exit(EXIT_FAILURE);
    }
    client_handle(sock);

    return 0;
}

void client_handle(int sock) {
    char sendbuf[MAXLEN], recvbuf[MAXLEN];
    bzero(sendbuf, MAXLEN);
    bzero(recvbuf, MAXLEN);
    int n = 0;

    while (1) {
        if (NULL == fgets(sendbuf, MAXLEN, stdin)) {
            break;
        }
        // Exit by `#`
        if ('#' == sendbuf[0]) {
            break;
        }
        struct timeval start, end;
        gettimeofday(&start, NULL);
        write(sock, sendbuf, strlen(sendbuf));
        n = read(sock, recvbuf, MAXLEN);
        if (n < 0) {
            perror("read failure.");
            exit(EXIT_FAILURE);
        }
        if (0 == n) {
            break;
        }
        write(STDOUT_FILENO, recvbuf, n);
        gettimeofday(&end, NULL);
        printf("time diff=%ld microseconds\n", ((end.tv_sec * 1000000 + end.tv_usec)- (start.tv_sec * 1000000 + start.tv_usec)));
    }

    close(sock);
}

Welcome to the WeChat Public Number and push technical articles such as computer network, back-end development, block chain, distribution, Rust, Linux, etc.

Posted by earthlingzed on Thu, 01 Aug 2019 18:44:45 -0700