} fprintf(stderr,\return -1; descr = pcap_open_live(dev,BUFSIZ,promisc,pcap_time_out,errbuf); if(descr == NULL){ } sprintf(filter_str,\ ip_dst[2],ip_dst[3]); if(pcap_compile(descr,&fp,filter_str,0,netp) == -1){ } printf(\ return -1; printf(\return -1; if(pcap_setfilter(descr,&fp) == -1){ } while(1){ printf(\packet=pcap_next(descr, &hdr); if(packet == NULL){ continue; } printf(\ return -1; l = libnet_init(LIBNET_LINK_ADV,device,errbuf); if (l == NULL){ fprintf(stderr, \ exit(EXIT_FAILURE); } t = libnet_build_arp( ARPHRD_ETHER, /* hardware addr */ ETHERTYPE_IP, /* protocol addr */ 6, /* hardware addr size */ 4, /* protocol addr size */ ARPOP_REPLY, /* operation type */ enet_src, /* sender hardware addr */ ip_src, /* sender protocol addr */ enet_dst, /* target hardware addr */ ip_dst, /* target protocol addr */ NULL, /* payload */ 0, /* payload size */ l, /* libnet handle */ 0); /* libnet id */ if (t == -1){ fprintf(stderr, \ goto bad; } t = libnet_autobuild_ethernet( enet_dst, /* ethernet destination */ ETHERTYPE_ARP, /* protocol type */ l); /* libnet handle */ if (t == -1){ fprintf(stderr, \\\n\ goto bad; } c = libnet_adv_cull_packet(l, &packet, &packet_s); if (c == -1){ fprintf(stderr, \ goto bad; } c = libnet_write(l); if (c == -1){ fprintf(stderr, \ goto bad; } continue; bad: libnet_destroy(l); return (EXIT_FAILURE); } libnet_destroy(l); return (EXIT_FAILURE); } int get_cmdline(int argc,char *argv[]){ char c; char string[]=\ while((c = getopt(argc, argv, string)) != EOF){ if(c=='d') *((unsigned int*)ip_dst)=(unsigned int)inet_addr(optarg); else if(c== 's') *((unsigned int*)ip_src)=(unsigned int)inet_addr(optarg); else if(c=='D') mac_strtochar6(enet_dst,optarg); else if(c=='S') mac_strtochar6(enet_dst,optarg); else if(c=='h') return 0; else return -1; } } return 1;
minitcpip协议软件系统框架 图3与图4是minitcpip协议软件系统的框架图。其中,minitcpip协议软件在一个单独的进程中实现。这个进程作为TCP/IP协议软件服务器建立C/S模型向应用程序提供服务。其通讯采用了命名管道建立C/S模型的方式,任何用户的应用进程对minitcpip的使用必须作为客户端,通过minisocket函数库进行。其通讯模型见图2。 协议软件进程一旦运行,则初始化libnet、libpcap,初始化TCP/IP连接管理表(TCB)以及接收与发送缓冲区,打开众所周知的FIFO等操作,然后等待客户机发来的命令(通过众所周知的FIFO)。在收到合法的命令,包括建立连接、发送数据、接收数据、关闭连接和设置连接属性等等之后,就作出相应的分析与处理。比如根据命令中指定的源IP、目的IP、源端口、目的端口开始监控和接收过滤网络上的数据(如下文,实际是监控三个文件描述符)等等。 为了方便监控和调试协议软件内部状态,协议软件同时等待标准输入设备发来的命令,协议软件将根据标准输入的合法命令形成到标准输出设备的输出。 除了在周知口等待客户机的命令、在标准输入设备等待监控命令之外,协议软件还必须同时等待来自网络设备的数据。为了在同一个进程中可以高效率的处理这些不同来源的数据,软件通过使用libpcap提供的函数接口int pcap_fileno(pcap_t *p),得到被打开的网络设备文件的文件描述符。在得到这个描述符之后,就可以和管道文件描述符同等的使用select()函数进行并行处理了。 在网络设备文件的文件描述字可读时,软件将调用u_char *pcap_next(pcap_t *p, struct pcap_pkthdr *h)函数来获得下一个抓到的数据包。 该协议软件的原理性的实现代码如下: main(){ 初始化libnet、libpcap;分配接收、发送缓冲区;初始化定时器;等等 while(1){ if(发送缓冲区有数据){ } 调用select()函数等待3个文件描述符是否准备好,这三个文件描述符分别是: PCAP文件描述符、周知口管道读文件描述符、标准输入文件描述符 if(PCAP文件描述符准备好){ } if(周知口读文件描述符准备好){ 调用pcap_next()函数来获得下一个抓到的数据包 发送数据 读取数据 根据进程内部的TCB中的信息,按照TCP协议规范进行分析处理 } if(标准输入文件描述符准备好){ 读取数据 分析处理,比如将内部信息回馈到标准输出文件描述符 } } if(超时){ } } 超时处理 如上面的流程所示,在收到网络上的数据包时,即根据TCP/IP协议IP、TCP等报文格式进行分析处理,并将接收到的数据回传给客户端应用程序。在收到周知管道的数据包时,则根据数据包的命令类型进行相应的操作,比如对其中的一条命令--SEND命令,在接收到这条命令后,就将其后附的数据写入发送缓冲区,在随后的循环中,这些数据将被依次发送到网络上去。在周知管道与回送管道上进行的通讯采用了一个自定义的协议,后文对此作了简单介绍。 最终系统在运行时的基本框架如下两图所示:其中,图3是系统在运行时的整体结构,图4是协议软件进程内部的结构。 TCP/IP协议数据处理模块是一组函数,与关键数据结构TCP表(TCB)等配合,负责实现TCP/IP协议的功能。
对minitcpip的协议实现的两点讨论
经过多年的发展,目前广泛应用的标准TCP/IP软件已经能够支持以太网、串行链路等多种物理设备,本文所讨论的实现主要是集中在以太网之上。
下面的讨论主要集中在定时器的设置和与操作系统的互斥两个问题上。
实际应用的TCP/IP协议软件是一个非常复杂的系统,具有流量控制和拥塞控制机制,一般都是由TCP/IP输出进程、输入进程、定时器进程等多个系统进程配合完成。而实现这些机制的一个重要的基础是定时器的设置与处理。本文中minitcpip由于是在单进程中实现,难以实现复杂的精确定时功能,所以在定时器的处理上进行了相当的简化。其中,TCP协议状态机中的TIME_WAIT状态定时间隔是一个基础参数,一旦minitcpip运行之后,这个参数就不会改变,或者只能通过标准输入口进行调试目的的人工修改;每一个TCP/IP连接的重发定时器的策略则是根据该连接所有的数据报的接收效果而建立的,具体实现是设置一个最小时间间隔参数和一个最大时间间隔参数,系统初始化后,重发定时间隔取最小值,此后每发生一次超时(没有收到该连接的任何数据报或者没有收到具有ACK标志且确认序号正确的数据报),就将该重发定时间隔翻倍,直到达到
最大值为止;如果在正确的收到若干数据报后都没有发生超时,就将重发定时时间间隔减半,直到达到最小数值。系统通过重发定时间隔与系统当前时钟减去保存的上次发送的时钟值的比较结果得出是否应该重新发送分组。而select()函数所使用的溢出时间则取所有连接的定时间隔的最小数值,以保证及时的发送分组。这样的策略当然效率不高,但是已经可以保证协议软件的正确运行。 另外,minitcpip协议软件是在本机同时存在一个标准TCP/IP协议栈的情况下实现的,BPF的原理是复制而不是截获本机收到的数据报文。因此,操作系统也会对收到的报文进行处理,这个问题如果处理不好,就会存在一些与操作系统内部的TCP/IP软件相互干扰的问题。比如在源主机上,如果指定的源端口已经打开,则势必要影响本来正常运行的程序,最后导致二者都不能正常工作。同时,在使用minitcpip与远端目标主机上的标准TCP/IP协议软件建立连接的时候,本机的操作系统也会收到目标主机的报文,并且发现这个报文指定的IP地址和端口并不在打开的端口表项中,于是操作系统认为收到了一个不合法的报文。许多操作系统对这一事件的反应是发送一个带有RST标志的报文出去,从而导致目标主机的连接复位。
这个问题有几种可能的解决办法:
1. 因为实现平台使用了开放源代码的Linux系统,所以可以考虑修改内核中TCP软件实现部分(TCP软件向应用进程提交数据的过滤部分)对不合法报文的缺省行为。
2. 如果目标主机上也使用minitcpip实现TCP/IP通讯,则我们可以修改标准TCP/IP数据报中标志位作用的定义,比如可以忽略所有收到的带有RST标志的数据报,而在需要发送复位数据报的时候,改用别的保留标志位来代替。
3. 客户机在建立TCP连接的时候,使用一个空闲的本网段的IP地址作为源IP地址。这样操作系统就会认为所有发往该IP地址的数据报都不是本机应该接收的,从而不做反应;然而我们的minitcpip却可以正常的收到这个数据报。当然,为了远端目标主机可以与这个\莫须有\的IP地址通讯,需要有一个ARP协议的运行进程,向所有发出请求的机器发送本机IP对应的MAC地址。前文中已经提供了