本文的copyleft归gfree.wind@gmail.com所有,使用GPL发布,可以自由拷贝,转载。但转载请保持文档的完整性,注明原作者及原链接,严禁用于任何商业用途。
作者:gfree.wind@gmail.com
博客:linuxfocus.blog.chinaunix.net
一般情况下,我们使用raw socket都是用于发送icmp包或者自己定义的包。最近我想用raw socket
自己去创建TCP的包,并完成3次握手。
在这个过程中,发现了几个问题:
1. 发现收不到对端返回的ACK,但是通过tcpdump却可以看到。这个通过修改创建socket的API参数得以纠正。
原来创建socket使用如下参数s = socket(AF_INET, SOCK_RAW, IPPROTO_TCP)。因为linux的raw socket不会把
不会把UDP和TCP的分组传递给任何原始套接口。——见《UNIX网络编程》28.3.
所以为了读到ACK包,我们创建的raw socket需要建立在数据链路层上。
s = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_IP))
这样该socket就可以读取到所有的数据包。当然这样的数据包无疑是非常庞大的,那么我们可以
使用bind和connect来过滤数据包。bind可以指定本地的IP和端口,connect可以指定对端的IP和端口。
2. 在修改完创建socket的API,我发现还有一个问题,到现在依然没有找到合适的解决方法。
通过链路层的raw socket,我们可以收到对端的ACK。但是因为linux内核也会处理TCP的握手过程,
所以,当它收到ACK的时候,会认为这是一个非法包,会直接发送一个RST给对端。这样我们拿到了这个ACK,
也没有实际的用处了——因为这个连接已经被RST了。
我想,内核之所以会直接发送RST,也许是因为我们创建的是raw socket,但是发送的确是TCP包,
当对端返回ACK时,内核根本不认为我们已经打开了这个TCP端口,所以会直接RST掉这个连接。
那么问题就在于,我们如何告诉内核,我们打开了TCP的这个端口。
我想过一个方法,除了一个raw socket,再创建一个真正的TCP socket。但是细想起来,这条路应该行不通。
在网上搜了半天,总算找到一个比较详细的解释了。原因给我猜想的相差不远,当我们使用raw socket来发送sync包时,内核的TCP协议栈并不知道你发送了sync包,所以当对端返回SYNC/ACK时,内核首先要处理这个包,发现它是一个SYNC/ACK,然而协议栈却不知道前面的sync,所以就认为这个包是非法的,于是就会发送RST来中止连接。
原文如下:
This is one of the most frequently asked question by someone who is experimenting with raw sockets and TCP/IP. It is known that the 'IP_HDRINCL' socket option allows you to include the IP header along with the data. Since TCP encapsulates the IP header, we can also build a TCP packet and send it over a network. But the problem is, a TCP connection can never be established this way. The scenario is as follows:
A TCP connection is always made by a three-way handshake. So, initially you send a 'SYN' packet to the remote machine. If it is actively listening on the port, you get a 'SYN/ACK' packet. So far so good. But before you can respond, your machine sends an 'ACK/RST' packet and connection attempt is ended. For the connection to be complete, instead of the 'RST' packet, your machine should be sending an 'ACK' to the remote machine.
The difference lies where the connection is exactly made. Although the programs are communicating after the connection is complete, the TCP connection is never between two programs but rather between the TCP stacks of the two machines. Here 'stack' means a layer of programs that communicates between each other. TCP stack stands for the protocol driver or the actual network transport protocol. Now lets look at exactly what happens when you send a 'SYN' packet...
Since you are using raw sockets ('SOCK_RAW') and not TCP/Stream sockets ('SOCK_STREAM') the TCP stack has no information about what you are doing at program level. And since the 'IP_HDRINCL' allows you to build any type of IP packet and send it along with the data, you can build a 'SYN' packet and send it to the TCP server program which is actively listening. But the point is that the 'SYN' packet is being sent from your program and not the stack. In other words the TCP stack of your machine has no idea how of sending the 'SYN' packet.
On the other side the 'SYN' packet is received by the stack at the remote machine and not exactly by the program. As with the case of the arrival of any 'SYN' packet, the stack at the remote machine responds with a 'SYN/ACK' packet. This packet is now received by the TCP stack of your machine. In other words, the incoming TCP packet ('SYN/ACK') will be processed by the stack. Since it has no information of the previous sent 'SYN' packet, it responds with a 'RST' packet, as in the case of any improper or unacceptable packet for a connection.
So the difference between sending and receiving a TCP packet using raw sockets is, the former is not processed while the latter is processed by the TCP stack of your machine.
该解释来自于www.codeguru.com, http://www.codeguru.com/forum/showthread.php?t=320739
感谢Mr Andreas Masur 和Mr Mathew Joy. Thanks Mr Andreas Masur and Mr Mathew Joy.