TCP 中出现RST的情况
http://www.360doc.com/content/13/0702/10/1073512_297069771.shtml
原 几种TCP连接中出现RST的情况
应该没有人会质疑,现在是一个网络时代了。应该不少程序员在编程中需要考虑多机、局域网、广域网的各种问题。所以网络知识也是避免不了学习的。而且笔者一直觉得TCP/IP网络知识在一个程序员知识体系中必需占有一席之地的。
在TCP协议中RST表示复位,用来异常的关闭连接,在TCP的设计中它是不可或缺的。发送RST包关闭连接时,不必等缓冲区的包都发出去,直接就丢弃缓存区的包发送RST包。而接收端收到RST包后,也不必发送ACK包来确认。
其实在网络编程过程中,各种RST错误其实是比较难排查和找到原因的。下面我列出几种会出现RST的情况。
1 端口未打开
服务器程序端口未打开而客户端来连接。这种情况是最为常见和好理解的一种了。去telnet一个未打开的TCP的端口可能会出现这种错误。这个和操作系统的实现有关。在某些情况下,操作系统也会完全不理会这些发到未打开端口请求。
比如在下面这种情况下,主机241向主机114发送一个SYN请求,表示想要连接主机114的40000端口,但是主机114上根本没有打开40000这个端口,于是就向主机241发送了一个RST。这种情况很常见。特别是服务器程序core dump之后重启之前连续出现RST的情况会经常发生。
当然在某些操作系统的主机上,未必是这样的表现。比如向一台WINDOWS7的主机发送一个连接不存在的端口的请求,这台主机就不会回应。
2 请求超时
曾经遇到过这样一个情况:一个客户端连接服务器,connect返回-1并且error=EINPROGRESS。 直接telnet发现网络连接没有问题。ping没有出现丢包。用抓包工具查看,客户端是在收到服务器发出的SYN之后就莫名其妙的发送了RST。
比如像下面这样:
有89、27两台主机。主机89向主机27发送了一个SYN,表示希望连接8888端口,主机27回应了主机89一个SYN表示可以连接。但是主机27却很不友好,莫名其妙的发送了一个RST表示我不想连接你了。
后来经过排查发现,在主机89上的程序在建立了socket之后,用setsockopt的SO_RCVTIMEO选项设置了recv的超时时间为100ms。而我们看上面的抓包结果表示,从主机89发出SYN到接收SYN的时间多达110ms。(从15:01:27.799961到15:01:27.961886, 小数点之后的单位是微秒)。因此主机89上的程序认为接收超时,所以发送了RST拒绝进一步发送数据。
3 提前关闭
关于TCP,我想我们在教科书里都读到过一句话,'TCP是一种可靠的连接'。 而这可靠有这样一种含义,那就是操作系统接收到的来自TCP连接中的每一个字节,我都会让应用程序接收到。如果应用程序不接收怎么办?你猜对了,RST。
看两段程序:
1 #include<sys/socket.h> 2 #include<errno.h> 3 #include<netinet/in.h> 4 #include<string.h> 5 #include<stdio.h> 6 7 #define SERV_PORT 60000 8 #define WAIT_COUNT 10 9 10 int main(int argc, char** argv) 11 12 { 13 14 int listen_fd, real_fd; 15 16 struct sockaddr_in listen_addr, client_addr; 17 18 socklen_t len = sizeof(struct sockaddr_in); 19 20 listen_fd = socket(AF_INET, SOCK_STREAM, 0); 21 22 if(listen_fd == -1) 23 24 { 25 26 perror("socket failed "); 27 28 return -1; 29 30 } 31 32 bzero(&listen_addr,sizeof(listen_addr)); 33 34 listen_addr.sin_family = AF_INET; 35 36 // listen_addr.sin_addr.s_addr = inet_addr("10.208.170.9"); 37 listen_addr.sin_addr.s_addr = htonl (INADDR_ANY); 38 39 40 listen_addr.sin_port = htons(SERV_PORT); 41 42 bind(listen_fd,(struct sockaddr *)&listen_addr, len); 43 44 listen(listen_fd, WAIT_COUNT); 45 46 while(1) 47 48 { 49 50 real_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &len); 51 52 if(real_fd == -1) 53 54 { 55 56 perror("accpet fail "); 57 58 return -1; 59 60 } 61 62 if(fork() == 0) 63 64 { 65 66 close(listen_fd); 67 68 char pcContent[4096]; 69 70 read(real_fd,pcContent,4096); 71 72 close(real_fd); 73 74 exit(0); 75 76 } 77 78 close(real_fd); 79 80 } 81 82 return 0; 83 84 }
1 if(fork() == 0) 2 3 { 4 5 close(listen_fd); 6 7 char pcContent[4096]; 8 9 read(real_fd,pcContent,4096); 10 11 close(real_fd); 12 13 exit(0); 14 15 }
每次只是读socket的前4096个字节,然后就关闭掉连接。
然后再看一下client的代码:
1 #include<sys/socket.h> 2 #include<errno.h> 3 #include<netinet/in.h> 4 #include<string.h> 5 #include<stdio.h> 6 7 #define BUF_LEN 1028 8 #define SERV_PORT 60000 9 #define SERV_IP "10.208.170.9" 10 //client.c 11 12 int main(int argc, char** argv) 13 14 { 15 16 int send_sk; 17 18 struct sockaddr_in s_addr; 19 20 socklen_t len = sizeof(s_addr); 21 22 send_sk = socket(AF_INET, SOCK_STREAM, 0); 23 24 if(send_sk == -1) 25 26 { 27 #include<sys/socket.h> 28 #include<errno.h> 29 #include<netinet/in.h> 30 #include<string.h> 31 #include<stdio.h> 32 33 #define BUF_LEN 1028 34 #define SERV_PORT 60000 35 #define SERV_IP "10.208.170.9" 36 //client.c 37 38 int main(int argc, char** argv) 39 40 { 41 42 int send_sk; 43 44 struct sockaddr_in s_addr; 45 46 socklen_t len = sizeof(s_addr); 47 48 send_sk = socket(AF_INET, SOCK_STREAM, 0); 49 50 if(send_sk == -1) 51 52 { 53 54 perror("socket failed "); 55 56 return -1; 57 58 } 59 60 bzero(&s_addr, sizeof(s_addr)); 61 62 s_addr.sin_family = AF_INET; 63 64 65 66 inet_pton(AF_INET,SERV_IP,&s_addr.sin_addr); 67 68 s_addr.sin_port = htons(SERV_PORT); 69 70 if(connect(send_sk,(struct sockaddr*)&s_addr,len) == -1) 71 72 { 73 74 perror("connect fail "); 75 76 return -1; 77 78 } 79 80 char pcContent[5000]={7,7,8,8}; 81 82 write(send_sk,pcContent,5000); 83 84 sleep(1); 85 86 close(send_sk); 87 88 }
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 96 bytes
22:55:13.816982 IP 10.208.170.10.55914 > 10.208.170.9.60000: S 1890505779:1890505779(0) win 5840 <mss 1460,sackOK,timestamp 3653881104 0,nop,wscale 7>
22:55:13.821485 IP 10.208.170.9.60000 > 10.208.170.10.55914: S 3237333984:3237333984(0) ack 1890505780 win 5792 <mss 1460,sackOK,timestamp 1499086014 3653881104,nop,wscale 6>
22:55:13.817360 IP 10.208.170.10.55914 > 10.208.170.9.60000: . ack 1 win 46 <nop,nop,timestamp 3653881105 1499086014>
22:55:13.817362 IP 10.208.170.10.55914 > 10.208.170.9.60000: . 1:2897(2896) ack 1 win 46 <nop,nop,timestamp 3653881105 1499086014>
22:55:13.817398 IP 10.208.170.9.60000 > 10.208.170.10.55914: . ack 2897 win 136 <nop,nop,timestamp 1499086014 3653881105>
22:55:13.817657 IP 10.208.170.10.55914 > 10.208.170.9.60000: . 2897:4345(1448) ack 1 win 46 <nop,nop,timestamp 3653881105 1499086014>
22:55:13.817673 IP 10.208.170.9.60000 > 10.208.170.10.55914: . ack 4345 win 181 <nop,nop,timestamp 1499086015 3653881105>
22:55:13.817658 IP 10.208.170.10.55914 > 10.208.170.9.60000: P 4345:5001(656) ack 1 win 46 <nop,nop,timestamp 3653881105 1499086014>
22:55:13.817694 IP 10.208.170.9.60000 > 10.208.170.10.55914: . ack 5001 win 272 <nop,nop,timestamp 1499086015 3653881105>
22:55:13.817845 IP 10.208.170.9.60000 > 10.208.170.10.55914: R 1:1(0) ack 5001 win 272 <nop,nop,timestamp 1499086015 3653881105>
前三行就是TCP的3次握手,从第四行开始看,客户端的49660端口向服务器的9877端口发送了5000个字节的数据,然后服务器端发送了一个ACK进行了确认,紧接着服务器向客户端发送了一个RST断开了连接。和我们的预期一致。
4 在一个已关闭的socket上收到数据
如果某个socket已经关闭,但依然收到数据也会产生RST。
代码如下:
客户端:
1 int main(int argc, char** argv) 2 3 { 4 5 int send_sk; 6 7 struct sockaddr_in s_addr; 8 9 socklen_t len = sizeof(s_addr); 10 11 send_sk = socket(AF_INET, SOCK_STREAM, 0); 12 13 if(send_sk == -1) 14 15 { 16 17 perror("socket failed "); 18 19 return -1; 20 21 } 22 23 bzero(&s_addr, sizeof(s_addr)); 24 25 s_addr.sin_family = AF_INET; 26 27 28 29 inet_pton(AF_INET,SER_IP,&s_addr.sin_addr); 30 31 s_addr.sin_port = htons(SER_PORT); 32 33 if(connect(send_sk,(struct sockaddr*)&s_addr,len) == -1) 34 35 { 36 37 perror("connect fail "); 38 39 return -1; 40 41 } 42 43 char pcContent[4096]={0}; 44 45 write(send_sk,pcContent,4096); 46 47 sleep(1); 48 49 write(send_sk,pcContent,4096); 50 51 close(send_sk); 52 53 }
服务器:
1 int main(int argc, char** argv) 2 { 3 4 int listen_fd, real_fd; 5 6 struct sockaddr_in listen_addr, client_addr; 7 8 socklen_t len = sizeof(struct sockaddr_in); 9 10 listen_fd = socket(AF_INET, SOCK_STREAM, 0); 11 12 if(listen_fd == -1) 13 14 { 15 16 perror("socket failed "); 17 18 return -1; 19 20 } 21 22 bzero(&listen_addr,sizeof(listen_addr)); 23 24 listen_addr.sin_family = AF_INET; 25 26 listen_addr.sin_addr.s_addr = htonl(INADDR_ANY); 27 28 listen_addr.sin_port = htons(SERV_PORT); 29 30 bind(listen_fd,(struct sockaddr *)&listen_addr, len); 31 32 listen(listen_fd, WAIT_COUNT); 33 34 while(1) 35 36 { 37 38 real_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &len); 39 40 if(real_fd == -1) 41 42 { 43 44 perror("accpet fail "); 45 46 return -1; 47 48 } 49 50 if(fork() == 0) 51 52 { 53 54 close(listen_fd); 55 56 char pcContent[4096]; 57 58 read(real_fd,pcContent,4096); 59 60 close(real_fd); 61 62 exit(0); 63 64 } 65 66 close(real_fd); 67 68 } 69 70 return 0; 71 72 }
总结
总结,本文讲了几种TCP连接中出现RST的情况。实际上肯定还有无数种的RST发生,我以后会慢慢收集把更多的例子加入这篇文章。
参考文献:
1 从TCP协议的原理来谈谈RST攻击 http://russelltao.iteye.com/blog/1405349
2 TCP客户-服务器程序例子http://blog.csdn.net/youkuxiaobin/article/details/6917880