- ·上一篇文章:使用C#设计一个简单的抓网络页面的程序
- ·下一篇文章:使用C#实现ADSL自动拨号
用C#的Raw Socket完成网络封包监视
其中,设置套接字选项时必须使套接字包含IP包头,否则无法填充IPHeader结构,也无法获得数据包信息。
int ret_code = socket.IOControl(SIO_RCVALL, IN, OUT);是函数中最关键的一步了,因为,在windows中我们不能用Receive函数来接收raw socket上的数据,这是因为,所有的IP包都是先递交给系统核心,然后再传输到用户程序,当发送一个raws socket包的时候(比如syn),核心并不知道,也没有这个数据被发送或者连接建立的记录,因此,当远端主机回应的时候,系统核心就把这些包都全部丢掉,从而到不了应用程序上。所以,就不能简单地使用接收函数来接收这些数据报。要达到接收数据的目的,就必须采用嗅探,接收所有通过的数据包,然后进行筛选,留下符合我们需要的。可以通过设置SIO_RCVALL,表示接收所有网络上的数据包。接下来介绍一下IOControl函数。MSDN解释它说是设置套接字为低级别操作模式,怎么低级别操作法?其实这个函数与API中的WSAIoctl函数很相似。WSAIoctl函数定义如下:
C#的IOControl函数不像WSAIoctl函数那么复杂,其中只包括其中的控制操作码、输入字节流、输出字节流三个参数,不过这三个参数已经足够了。我们看到函数中定义了一个字节数组:byte []IN = new byte[4]{1, 0, 0, 0}实际上它是一个值为1的DWORD或是Int32,同样byte []OUT = new byte[4];也是,它整和了一个int,作为WSAIoctl函数中参数lpcbBytesReturned指向的值。
因为设置套接字选项时可能会发生错误,需要用一个值传递错误标志:
下面的函数实现的数据包的接收:
大家注意到了,在上面的函数中,我们使用了指针这种所谓的不安全代码,可见在C#中指针和移位运算这些原始操作也可以给程序员带来编程上的便利。在函数中声明PacketArrivedEventArgs类对象,以便通过OnPacketArrival(e)函数通过事件把数据包信息传递出去。其中PacketArrivedEventArgs类是RawSocket类中的嵌套类,它继承了系统事件(Event)类,封装了数据包的IP、端口、协议等其他数据包头中包含的信息。在启动接收数据包的函数中,我们使用了异步操作的方法,以下函数开启了异步监听的接口:
int ret_code = socket.IOControl(SIO_RCVALL, IN, OUT);是函数中最关键的一步了,因为,在windows中我们不能用Receive函数来接收raw socket上的数据,这是因为,所有的IP包都是先递交给系统核心,然后再传输到用户程序,当发送一个raws socket包的时候(比如syn),核心并不知道,也没有这个数据被发送或者连接建立的记录,因此,当远端主机回应的时候,系统核心就把这些包都全部丢掉,从而到不了应用程序上。所以,就不能简单地使用接收函数来接收这些数据报。要达到接收数据的目的,就必须采用嗅探,接收所有通过的数据包,然后进行筛选,留下符合我们需要的。可以通过设置SIO_RCVALL,表示接收所有网络上的数据包。接下来介绍一下IOControl函数。MSDN解释它说是设置套接字为低级别操作模式,怎么低级别操作法?其实这个函数与API中的WSAIoctl函数很相似。WSAIoctl函数定义如下:
int WSAIoctl( SOCKET s, //一个指定的套接字 DWORD dwIoControlCode, //控制操作码 LPVOID lpvInBuffer, //指向输入数据流的指针 DWORD cbInBuffer, //输入数据流的大小(字节数) LPVOID lpvOutBuffer, // 指向输出数据流的指针 DWORD cbOutBuffer, //输出数据流的大小(字节数) LPDWORD lpcbBytesReturned, //指向输出字节流数目的实数值 LPWSAOVERLAPPED lpOverlapped, //指向一个WSAOVERLAPPED结构 LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine//指向操作完成时执行的例程 ); |
C#的IOControl函数不像WSAIoctl函数那么复杂,其中只包括其中的控制操作码、输入字节流、输出字节流三个参数,不过这三个参数已经足够了。我们看到函数中定义了一个字节数组:byte []IN = new byte[4]{1, 0, 0, 0}实际上它是一个值为1的DWORD或是Int32,同样byte []OUT = new byte[4];也是,它整和了一个int,作为WSAIoctl函数中参数lpcbBytesReturned指向的值。
因为设置套接字选项时可能会发生错误,需要用一个值传递错误标志:
public bool ErrorOccurred { get { return error_occurred; } } |
下面的函数实现的数据包的接收:
//解析接收的数据包,形成PacketArrivedEventArgs事件数据类对象,并引发PacketArrival事件 unsafe private void Receive(byte [] buf, int len) { byte temp_protocol=0; uint temp_version=0; uint temp_ip_srcaddr=0; uint temp_ip_destaddr=0; short temp_srcport=0; short temp_dstport=0; IPAddress temp_ip; PacketArrivedEventArgs e=new PacketArrivedEventArgs();//新网络数据包信息事件 fixed(byte *fixed_buf = buf) { IPHeader * head = (IPHeader *) fixed_buf;//把数据流整和为IPHeader结构 e.HeaderLength=(uint)(head->ip_verlen & 0x0F) << 2; temp_protocol = head->ip_protocol; switch(temp_protocol)//提取协议类型 { case 1: e.Protocol="ICMP"; break; case 2: e.Protocol="IGMP"; break; case 6: e.Protocol="TCP"; break; case 17: e.Protocol="UDP"; break; default: e.Protocol= "UNKNOWN"; break; } temp_version =(uint)(head->ip_verlen & 0xF0) >> 4;//提取IP协议版本 e.IPVersion = temp_version.ToString(); //以下语句提取出了PacketArrivedEventArgs对象中的其他参数 temp_ip_srcaddr = head->ip_srcaddr; temp_ip_destaddr = head->ip_destaddr; temp_ip = new IPAddress(temp_ip_srcaddr); e.OriginationAddress =temp_ip.ToString(); temp_ip = new IPAddress(temp_ip_destaddr); e.DestinationAddress = temp_ip.ToString(); temp_srcport = *(short *)&fixed_buf[e.HeaderLength]; temp_dstport = *(short *)&fixed_buf[e.HeaderLength+2]; e.OriginationPort=IPAddress.NetworkToHostOrder(temp_srcport).ToString(); e.DestinationPort=IPAddress.NetworkToHostOrder(temp_dstport).ToString(); e.PacketLength =(uint)len; e.MessageLength =(uint)len - e.HeaderLength; e.ReceiveBuffer=buf; //把buf中的IP头赋给PacketArrivedEventArgs中的IPHeaderBuffer Array.Copy(buf,0,e.IPHeaderBuffer,0,(int)e.HeaderLength); //把buf中的包中内容赋给PacketArrivedEventArgs中的MessageBuffer Array.Copy(buf,(int)e.HeaderLength,e.MessageBuffer,0,(int)e.MessageLength); } //引发PacketArrival事件 OnPacketArrival(e); } |
大家注意到了,在上面的函数中,我们使用了指针这种所谓的不安全代码,可见在C#中指针和移位运算这些原始操作也可以给程序员带来编程上的便利。在函数中声明PacketArrivedEventArgs类对象,以便通过OnPacketArrival(e)函数通过事件把数据包信息传递出去。其中PacketArrivedEventArgs类是RawSocket类中的嵌套类,它继承了系统事件(Event)类,封装了数据包的IP、端口、协议等其他数据包头中包含的信息。在启动接收数据包的函数中,我们使用了异步操作的方法,以下函数开启了异步监听的接口:
public void Run() //开始监听 { IAsyncResult ar = socket.BeginReceive(receive_buf_bytes, 0, len_receive_buf, SocketFlags.None, new AsyncCallback(CallReceive), this); } |
用C#的Raw Socket完成网络封包监视