는 TCP RST 세그먼트와 공통의 현장 분석을 재설정

RST계속 존재하는 것은 더 이상 필요하지 않습니다 이러한 연결을 닫는 연결 재설정, 나타냅니다. 정상적인 상황에서는 그 이상 연결 폐쇄 나타내는 법선 사이의 차이와 연결을 종료하기 위해 네 번 끊었다.

생성 RST세 가지 조건은 다음과 같습니다 :

  1. 의 포트에 대한 운명 SYN도착,하지만이 포트에 서버를 듣고있다;
  2. 기존의 TCP 연결을 취소하려면;
  3. 섹션에 TCP 연결을받는 것은 존재하지 않았다.

몇 가지 시나리오입니다 다음, 우리는해야합니다 RST리셋 세그먼트의 사용을 설명하기 위해.

포트가 존재하지 않는 연결에 대한 요청

클라이언트는 서버 요청에 연결된 포트를 시작 SYN하지만, 호스트 서버 포트의 목적은 클라이언트에 응답이 시간에 존재하지 않는 RST, 해제 요청.

다음은 프로그램 캡처하여 분석 하였다. 프로그램 소스 코드를 다음과 같이 :

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

대상 호스트에 위의 프로그램은 192.168.2.229대상 호스트와 포트가 시작되지 않는 반면, TCP 연결을 시작하는 33333서비스를 듣고. 그래서 로컬 호스트가 대상 호스트에 대한 TCP 연결을 시작할 때, 당신은 대상 호스트로부터받은 것 RST, 그리고 분리. (물론 모든 반환 RST, 일부 호스트가 응답하지 않을 수 있습니다). 다음과 같이 미묘한 :

대상 호스트에 로컬 호스트는 TCP 연결을 전송합니다 SYN:
여기에 그림 삽입 설명

로컬 호스트 대상 호스트 회신 ACK、RST:
여기에 그림 삽입 설명

두, 연결을 종료

파티에 의해 정상적인 통신을 전송하여 연결을 종료합니다 FIN. 이 방법은 또한 순차적 방출로 알려져있다. 때문에 FIN그는 결국 발송 데이터가 전송되기 전에 대기, 데이터 손실의 상황은 일반적으로 발생하지 않습니다. 그러나 어느 순간, 우리는 세그먼트 리셋 패킷 전송하여 수 RST대안을 FIN연결을 종료합니다. 이러한 접근 방식은 또한 릴리스의 종료라고합니다.

연결이 응용 프로그램의 두 가지 주요 특징을 제공 할 수 있습니다 종료 :

  • 모든 데이터가 폐기 될 리셋 구간은 즉시 전송 될 큐;
  • 重置报文段的接收方会说明通信的另一端采用了终止的方式而不是一次正常关闭。API必须提供一种实现上述终止行为的方式来取代正 常的关闭操作。

套接字API可通过套接字选项SO_LINGER的数值设置为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.  */
  };

SO_LINGER的不同值的含义如下:

  1. l_onoff0l_linger的值被忽略,内核缺省情况,close()调用会立即返回给调用者,TCP模块负责尝试发送残留的缓存区数据。
  2. l_onoff为非零值,l_linger0,则连接立即终止,TCP将丢弃残留在发送缓冲区中的数据并发送RST给对方,而不是发送FIN,这样避免了TIME_WAIT状态,对方read()时将收到Connection reset by peer的错误。
  3. l_onoff为非零值,l_linger大于零:如果l_linger时间范围,TCP模块成功发送完残留的缓冲区数据,则正常关闭,如果超时,则向对方发送RST,丢弃残留在发送缓冲区的数据。

客户端代码间附录代码1,服务端代码如下:

/* 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);    //设置SO_LINGER option值为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);  //接收数据后就主动关闭连接,用于RST测试          
            } 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;
}

抓包结果如下:
여기에 그림 삽입 설명
先3次握手,后客户端向服务度发送了5个字节数据,服务端在接收完5字节数据向客户端ACK后,表示想中断连接,此时因设置了SO_LINGER选项值为0close()时,直接向对方发送RST而不是正常的发送FIN,连接立即终止,并且不会有TIME_WAIT状态,TCP将丢弃残留在发送缓冲区中的数据,对方read()时将收到Connection reset by peer的错误。

三、半开连接

如果在未告知另一端的情况下通信的一端关闭或终止连接,那么就认为该条TCP连接处于半开状态。

举个例子,服务器主机被切断电源后重启(切断电源前可将网线断开,重启后再接上),此时的客户端是一个半开的连接。当客户端再次向服务端发送数据时,服务端对此连接一无所知,会回复一个重置报文段RST后,中断连接。

再或者如果程序开启了TCP保活机制,则当监测到对方主机不可达时,发送RST中断连接。详细可参考我的另一篇博文TCP保活机制

TCP连接如果长时间没有数据收发,会使TCP发送保活探测报文,以维持连接或者探测连接是否存在。
여기에 그림 삽입 설명
可以看到如果认为连接不存在了,就会发送RST中断连接。

四、提前关闭连接

TCP应用程序接收数据是从操作系统中接收的TCP数据,如果数据到达了操作系统但是我应用数据不想继续接收数据了,此时RST中断连接。

服务端代码:

/* 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);      //只接收部分数据就主动关闭连接,用于RST测试         
#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);  //只接收部分数据就主动关闭连接,用于RST测试
#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;
}

客户端发起连接后发送超过5字节的数据后,因为服务端只接收5个字节数据后不再接收数据(此时服务端操作系统已经收到10个字节数据,但上层read系统调用只接收5个字节),服务端向客户端发送RST中断连接。抓包结果如下:
여기에 그림 삽입 설명
先3次握手,握手后客户端发送了10字节长度的数据,服务端在回应客户端ACK接收到数据后,发送RST中断连接。

五、在一个已关闭的TCP连接上收到数据

如果一个已关闭的TCP连接又收到数据,显然是异常的,此时应RST中断连接。

服务端其他代码与上个代码相同,下面函数替换一下

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);  //接收数据后就主动关闭连接,用于RST测试          
            } 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;
}

客户端代码与上个相同,只有下面函数不同,替换一下即可:

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;
        }
        // 按`#`号退出
        if ('#' == sendbuf[0]) {
            break;
        }
        struct timeval start, end;
        gettimeofday(&start, NULL);
        write(sock, sendbuf, strlen(sendbuf));
        sleep(2);
        write(sock, sendbuf, strlen(sendbuf));      //这里是测试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);
}

미묘한 다음과 같이
여기에 그림 삽입 설명
제 3 방향 핸드 쉐이크를 상기 클라이언트가 서버로 전송 한 후에 데이터의 5 바이트가 데이터 서버의 5 바이트가 응답을 수신 ACK클라이언트에 전송 한 후, FIN상기 접속을 종료하지만,이 시간은 클라이언트에 대한 추가 데이터가 존재 서버에 시작되지, 전송 FIN,이 시간은 두 손을 흔들었다, ED 서비스를 클라이언트가 데이터의 다섯 바이트를 보내지 만 서버가 연결을 호출이 시간이 된 후 close()다시 연결을 수신,이 시간을 폐쇄 데이터가 비정상에 응답 속한 RST분리.

VI 부록

클라이언트 코드는 테스트에 사용

#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;
        }
        // 按`#`号退出
        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);
}

마이크로 채널 대중 숫자에 오신 것을 환영합니다 관심, 컴퓨터 네트워크를 밀어 백엔드 개발, 블록 체인, 분산, 녹, 리눅스 및 기타 기술 기사를!

추천

출처www.cnblogs.com/s-lisheng/p/11286708.html