libnet源码的分析

对每个要发送的包,libnet维护一个libnet­_t结构,这个结构是理解整个libnet的关键,也是libnet得以实现它强大功能的关键。让我们先从它入手,从整体到细节地揭开libnet的面纱。下面左图是libnet_t这个结构的示例。

 

其中的fd就是发送数据包将要用到的socket套接字,injection_type将会被设置成libnet_init()中的第一个参数,即你选择发送的方式,是基于link_layer的链路层数据包?还是基于IP层的raw数据包?后一种情况又分为IP4和IP6两种。protocol_blocks 和protocol_end都是指针,指向一个libnet自定义的libnet_pblock_t结构,由此管理一个libnet_pblock_t的链表。而libnet_pblock_t则表述各个协议,维护各个协议给发送的数据包添加的数据块,它的具体选项下面再说。link_type表示链路层的类型,link_offset则指向链路层也就是最底层协议包头的偏移地址。aligner是为了维护最后的数据包的字对齐而设置的,字符串指针device则指向通讯所用的设备,比如eth0.state是一个结构,与ptag_state一起,记录包在建立过程中的一些信息。label是一个字符数组。每当有错误发生的时候,errbuf数组就被用来记录错误信息。全部的数据包长度和保存于total_size中。

从libnet_t已经大概可以看出libnet的设计思想了:程序员决定一些参数,并且通过函数调用中的参数把相关的数据交给libnet.libnet则在程序员每要求购建一个协议包头的时候,为其创建一个libnet_pblock_t的结构保存这些数据,并将该结构入链表。当所有的准备工作完成,程序员一声令下,libnet就将协议块链(由protocol_blocks开始,终于pblock_end)中的协议包头以及数据组合成一个合乎规格的包,通过硬件发送出去。如果任何一个步骤出了差错,程序员都可以从errbuf中获取出错信息。最后,libnet从程序员手中接过一个指令,进行所有的善后工作。

每种协议的包头将在前期被实现为一个libnet_pblock_t的结构。libnet为它所支持的协议都定义了相应的数据结构,例如TCP包头的定义:

struct libnet_tcp_hdr

{

    u_int16_t th_sport;       /* source port */

    u_int16_t th_dport;       /* destination port */

    u_int32_t th_seq;          /* sequence number */

    u_int32_t th_ack;          /* acknowledgement number */

#if (LIBNET_LIL_ENDIAN)

    u_int8_t th_x2:4,         /* (unused) */

           th_off:4;        /* data offset */

#endif

#if (LIBNET_BIG_ENDIAN)

    u_int8_t th_off:4,        /* data offset */

           th_x2:4;         /* (unused) */

#endif

    u_int8_t  th_flags;       /* control flags */

    …….

    u_int16_t th_win;         /* window */

    u_int16_t th_sum;         /* checksum */

    u_int16_t th_urp;         /* urgent pointer */

};

它的长度是:

#define           LIBNET_TCP_H      0x14    /**< TCP header:          20 bytes */

当程序员调用libnet_built_tcp()构建一个TCP包头时,libnet会分配一个这样的结构,并且按照程序员的意愿填充这个结构的各个字段。并且生成一个libnet_pblock_t结构,使它的buf指针指向这个数据包头结构,并用b_len保存这个包头的长度(20字节)。这个libnet_pblock_t的其它相关字段将会被按规则填充。之后,这个libnet_pblock_t会被加入到libnet_t结构所维护的协议块链的适当位置。所谓“适当位置”,将会在后面加以说明。下图是进程中一种可能出现的情形:

 

利益于这种精巧的整体架构,libnet的具体实现就不显得困难了,而我们对它的理解也因此受益。剩下来的只是一些细节性的东西需要去把握,这一点显然更适合亲自去阅读代码。

下面是大概的处理流程。

在开始之前,需要插入一点说明:libnet使用条件编译的方式消除平台间的差异,由此产生了很多同名的函数(当然它们都在内部被调用),比如libnet_open_link()有五个。程序在运行时选择哪一个调用取决于你使用的平台或者你强加于编译器的预编译选项。

 

一般情况下事情会从libnet_init()开始。libnet_init()首先开启系统的网络功能:在linux下面,它会验证是否具有超级用户权限(这也就意味着低权限的用户不能使用成功),而如果是在windows下面,则会调用大名鼎鼎的WSAStartup()函数。之后分配一块内存区域建立libnet_t结构。在简单的初始化后,根据程序员传入的发送类型参数,进行相应的操作:如果是基于链路层的发送方式(LIBNET_LINK,LIBNET_LINK_ADV),则会向操作系统申请设备,并开启底层的socket服务。如果是基于IP层的发送方式,则开启该层的socket服务。

准备工作做好之后,转入直接的建包工作,这一工作实际表现为建一系列的协议包头。程序员需要按照自顶向下的顺序建协议包头,而要发送的正文数据往往被作为第一个协议包头的“有效荷载(payload)”被加载。此外,某些协议可能具有可选数据项,这一部分被libnet独立出来,由一个单独的libnet_pblock_t来负责,libnet同时也为此提供相应的功能函数,其名称被定为libnet_build_*_options().在这种情况下,libnet会自动调整这些协议块在链表中的位置,使payload的数据块在前面,中间是options块,其后跟随着该层协议的固定包头。

在合适的时候,libnet会把计算校验和这样的繁琐的工作优雅地完成,你甚至感觉不到它已经为你这样做了。

这样,只要程序员的使用正确,数据以及协议包头已经在链表中按顺序排列了,这为简化后面的工作极为有益。在libnet_write()过程中,链表中的数据被按顺序地拷贝到一个大的缓冲内。拷贝虽然是按照从高层协议到低层协议的顺序进行,但是却是从缓冲的后部向前部拷贝,从这里,我们可以看到“协议栈”的思想在闪光。

当一切工作完成之后,程序员简单地调用libnet_destroy()函数,关闭使用的网络通讯设备,释放占用的内存。程序员的工作是如此地简单:他甚至仅仅需要传递一个参数给这个简短的函数。

 

除了完成这样一些最直接的工作以外,libnet还提供了一组丰富实用的功能。它提供的地址解析功能能够实现IP地址在网络字节顺序、域名、点分形式之间的转换,它也提供了一套完整的随机数生成方案供你使用,如此等等。这一些功能使得它得以从一套单纯的库跃升为一个完整的系统。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值