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_onoff为0,l_linger的值被忽略,内核缺省情况,close()调⽤会⽴即返回给调⽤者,TCP模块负责尝试发送残留的缓存区数据。
2. l_onoff为⾮零值,l_linger为0,则连接⽴即终⽌,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选项值为0,close()时,直接向对⽅发送RST⽽不是正常的发送FIN,连接⽴即终⽌,并且不会有TIME_WAIT状态,TCP将丢弃残留在发送缓冲区中的数据,对⽅read()时将收到Connection reset by peer的错误。
三、半开连接
如果在未告知另⼀端的情况下通信的⼀端关闭或终⽌连接,那么就认为该条TCP连接处于半开状态。
举个例⼦,服务器主机被切断电源后重启(切断电源前可将⽹线断开,重启后再接上),此时的客户端是⼀个半开的连接。当客户端再次向服务端发送数据时,服务端对此连接⼀⽆所知,会回复⼀个重置报⽂段RST后,中断连接。
再或者如果程序开启了TCP保活机制,则当监测到对⽅主机不可达时,发送RST中断连接。详细可参考我的另⼀篇博⽂。
TCP连接如果长时间没有数据收发,会使TCP发送保活探测报⽂,以维持连接或者探测连接是否存在。
可以看到如果认为连接不存在了,就会发送RST中断连接。
四、提前关闭连接
socket通信报文格式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);
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论