本文的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.
該解釋來自于,
感謝Mr Andreas Masur 和Mr Mathew Joy. Thanks Mr Andreas Masur and Mr Mathew Joy.
作者:gfree.wind@gmail.com