第5章直接网络编程第5章直接网络编程随着计算机网络复杂性和规模的不断增长,迫切需要一些网络工具来分析、诊断和测试网络,保证网络的正常运行和安全性。这些网络诊断测试和安全工具工作在网络较低的层次(链路层或网络层),不能采用常规的网络编程方法。本章介绍几种不同的直接网络编程方法。第5章直接网络编程第5章直接网络编程5.1原始套接字编程5.2基于Winpcap的网络数据包捕获技术5.3基于Libnet的网络数据包构造技术习题与思考题第5章直接网络编程5.1原始套接字编程5.1.1概念原始套接字可以对底层的传输协议加以控制,对IP头信息进行实际的操作,通过它可以模拟一些IP的实用工具,如Tracert和Ping等。原始套接字(rawsocket)是使用SOCK_RAW这个套接字类型来创建的。目前WinSock2.2提供了对它的支持。利用原始套接字可直接访问网络协议数据。第5章直接网络编程本节重点讨论利用IP层协议Internet控制消息协议(ICMP)进行原始套接字编程。为了说明问题,运用这种原始套接字来模拟IP的一些实用工具,如Ping和Tracert程序等。Ping这个实用程序探测到某个主机的路由是否有效和畅通,看看对方的机器是否会作出响应。对程序开发者来说,经常都要用到一种程序化的方法,以便判断一台机器是否“活动”,网络数据能否抵达它。IP多播通信利用IGMP将多播组成员信息通知路由器。大多数Win32平台目前都增加了对IGMP第2版的支持。但在某些情况下,我们也需要送出自己的IGMP数据包,以便脱离组成员关系。第5章直接网络编程创建原始套接字可用Socket或WSASocket函数。在套接字的创建过程中,必须自行设定SOCK_RAW标志。下述代码片断解释了如何将ICMP作为一种基层IP协议来完成一个原始套接字的创建:SOCKETs;s=socket(AF_INET,SOCK_RAW,IPPROTO_ICMP);if(s==INVALID_SOCKET){//创建套接字失败}第5章直接网络编程在完成了原始套接字的创建之后,就可以在发送及接收调用中使用对应的套接字句柄。创建原始套接字时,无论是否设定了IP_HDRINCL选项,IP头都会包含在接收到的任何返回数据中。上述代码中采用的是ICMP。当使用IGMP、UDP、IP或者原始IP时,只需分别设置IPPROTO_IGMP、IPPROTO_UDP、IPPR-OTO_IP或IPPROTO_RAW即可。但值得注意的是,Windows95无法使用原始套接字;在Windows98/NT的操作系统中,创建原始套接字时,只能使用IGMP(IPPROTO_IGMP)和ICMP(IPPROTO_ICMP)两种协议;而Windows2000/XP在前者基础上还能够处理IP(IPPROTO_RAW)、TCP(IPPROTO_TCP)及UDP(IPPROTO_UDP)。第5章直接网络编程由于使用原始套接字可以控制基层传输机制,存在潜在的安全隐患,因此,在WindowsNT/2000/XP上,只有属于“管理员”(administrator)组的成员,才有权创建类型为SOCK_RAW的套接字。要绕过这一限制,可禁止对原始套接字的安全检查。方法是在注册表中创建变量HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\Afd\Parameters\DisableRawSecuri-ty,并将它的值设为1(DWORD类型),然后重新启动计算机。在Windows9x中,系统未对SOCK_RAW的使用加以限制。第5章直接网络编程5.1.2ICMP实现Ping程序的实现方法是,主机向远程计算机发出ICMP回应请求以后,远程计算机会处理这个请求,然后生成一条回应应答消息,再通过网络传回给发送主机;假如由于某些原因,不能抵达目标主机,就会生成对应的ICMP错误消息(比如“目标主机不可到达”),由那个路径上某处的一个路由器返回。假定与远程主机的物理性连接并不存在问题,但远程主机已经关机或没有设置对网络事件作出响应,便需由自己的程序来执行超时检查,来检测这样的情况。第5章直接网络编程Ping示例在程序中采取如下步骤:(1)创建类型为SOCK_RAW的一个套接字,同时设定协议IPPROTO_ICMP。(2)创建并初始化ICMP头。(3)调用sendto或WSASendto,将ICMP请求发给远程主机。(4)调用recvfrom或WSARecvfrom,以接收任何ICMP响应。接下来的代码示范了如何初始化并发送一个ICMPecho请求:第5章直接网络编程//定义ICMP头typedefstructicmp_hdr{unsignedcharicmp_type;unsignedcharicmp_code;unsignedshorticmp_checksum;unsignedshorticmp_id;unsignedshorticmp_sequence;unsignedlongicmp_timestamp;}ICMP_HDR,*PICMP_HDR,FAR*LPICMP_HDR;第5章直接网络编程ICMP_HDR*icmp=NULL;SOCKETs;SOCKADDR_STORAGEdest;charbuf[sizeof(ICMP_HDR)+32];icmp=(ICMP_HDR*)buf;icmp-icmp_type=8;//定义echo请求类型icmp-icmp_code=0;icmp-icmp_id=GetCurrentProcessId();icmp-icmp_checksum=0;//将校验和域清零第5章直接网络编程icmp-icmp_sequence=0;icmp-icmp_timestamp=GetTickCount();memset(&buf[sizeof(ICMP_HDR)],'@',32);//随机填充负载端//计算ICMP头和负载的校验和,checksum()函数的实现参见下文Ping程序icmp-icmp_checksum=checksum(buf,sizeof(ICMP_HDR)+32);s=socket(AF_INET,SOCK_RAW,IPPROTO_ICMP);第5章直接网络编程//初始化目标SOCKADDR_STORAGE((SOCKADDR_IN*)&dest)-sin_family=AF_INET;((SOCKADDR_IN*)&dest)-sin_port=htons(0);((SOCKADDR_IN*)&dest)-sin_addr.s_addr=inet_addr(1.2.3.4);sendto(s,buf,sizeof(ICMP_HDR)+32,0,(SOCKADDR*)&dest,sizeof(dest));第5章直接网络编程ICMP和ICMPv6(基于IPv6的ICMP)的echo请求的唯一差别是计算ICMP头中的校验和。在IPv4中,校验和仅简单地针对ICMP头和负载,但在IPv6中,因校验和应包括IPv6伪头、ICMPv6头和负载进行处理而较为复杂。这就意味着Ping程序必须知道发送IPv6数据包的源和目标地址,以获得IPv6头计算ICMPv6请求。第5章直接网络编程5.1.3Tracert对IP网络来说,另一个非常有用的工具是Tracert(路由追踪)。在Windows操作系统中,一般可以直接运行tracert.exe来调用这个工具。利用它可侦测出为抵达网络内任何一个指定的主机,中途需经过哪些路由器,以及它们的IP地址是什么。Tracert的设计原理是令其向目的地发送一个数据包,并重复递增IP的“存活时间”(TTL)值。刚开始的时候,TTL等于1;也就是说,一旦它抵达路途中的第一个路由器,TTL首先会超时(变成0),这样便会造成路由器生成一个ICMP“超时”数据包。随后,最初的TTL值递增1,以便UDP包能继续传到下一个路由器,而生成的ICMP超时包会自第一个路由器返回。只需将返回的每一条ICMP消息都收集下来,便能为中途经过的路由器IP地址勾勒出一个清晰的轮廓,直到最终的目标主机。第5章直接网络编程之所以认为Tracert是一个有用的工具,是由于它为我们提供了与途中经历的路由器有关的大量信息。对具体的应用程序来说,尽管需要执行Tracert的机会比Ping少得多,但对某些特定的任务而言,只有Tracert才能胜任。实现Tracert程序,只需将ICMP数据包简单地发给目的地,同时连续递增更改TTL的值。在TTL“超时”的时候,这样做也会造成一条ICMP错误消息的返回。注意这种方法和Ping有着某些共通之处,它也只要求使用一个套接字(安装ICMP协议)。本书附录6列举了Tracert的完整源代码,有兴趣的读者可以参考。第5章直接网络编程5.1.4IP_HDRINCL的使用对原始套接字来说,它存在的一项限制是只能对已经定义好的协议(如IGMP和ICMP等)进行操作,而不能用IPPROTO_UDP来创建一个新的原始套接字,也不能对UDP头进行操作;TCP也是一样的。要想对IP头、TCP头、UDP头或封装在IP内的其他任何协议进行操作,必须在使用原始套接字的同时使用IP_HDRINCL选项。利用该套接字选项,用户可以自行构建IP等任何协议头。第5章直接网络编程此外,要在应用程序中实现自定义的协议方案,并将其封装到IP内,必须首先创建一个原始套接字,然后将协议类型设为IPPROTO_RAW。这样,用户就可在IP头内自定义协议字段,同时构建自己的定制协议头。本小节将讲述如何自定义一个UDP数据包,使用户对其中牵涉到的步骤有一个比较全面的了解。一旦理解了如何操作UDP头,就可以很方便地实现自定义的协议头,或对IP内封装的其他协议进行处理。第5章直接网络编程使用IP_HDRINCL选项时,必须针对每一次发送和调用,向IP头内自行填充内容。同时,还要填写封装在其中的其他任何协议头。如下文所示,可以创建一个原始套接字,然后设置IP_HDRINCL标志。SOCKETs;BOOLbOpt;第5章直接网络编程s=WSASocket(AF_INET,SOCK_RAW,IPPROTO_UDP,NULL,0,WSA_FLAG_OVERLAPPED);ret=setsockopt(s,IPPROTO_IP,IP_HDRINCL,(char*)&bOpt,sizeof(bOpt));注意,我们现已创建了一个协议为IPPROT_UDP的原始套接字。这种做法只适用于Windows2000,因为它同时也要求设置IP_HDRINCL选项。第5章直接网络编程5.2基于Winpcap的网络数据包捕获技术许多网络应用通常需要分析网络中的数据流,查找网络故障或安全问题。要分析网络数据流,首先要能从网络中得到数据包。在一个实际的系统中,数据的收/发是由网卡来完成的,网卡接收到传输来的数据,然后检查数据帧的目的MAC地址,根据计算机上的网卡驱动程序设置的接收模式判断接收与否。如接收,则产生中断信号通知CPU;如不接收,则将数据帧丢弃。CPU得到中断信号产生中断,操作系统接收数据。通常网卡有如下接收模式:(1)广播模式:能够接收网络中的广播信息。第5章直接网络编程5.2基于Winpcap的网络数据包捕获技术(2)组播模式:能够接收组播数据。(3)直接模式:只有目的网卡才能接收该数据。(4)混杂模式:能够接收通过它的一切数据,而不管该数据是否时传给它的。目前有两种方法可以从网络中获得数据包,一种是采用专用硬件,另一种是利用计算机的网络接口(网络适配器),由软件来完成数据帧的获得,这种方法的前提是将本地NIC工作状态设成混杂模式,扑捉与其连接的物理媒体上传输的所有数据。第5章直接网络编程5.2基于Winpcap的网络数据包捕获技术虽然由软件来扑获的方法在性能上比不上专用硬件,但它更廉价,且易于修改和更新。基于以上原因,用软件扑获的方法得到了广泛的使用和认同。