linux内核太构造数据包的第二种方式就是直接调用dev_queue_xmit函数,将构造完毕的数据包直接发送到网卡驱动。从NF框架来看,该函数的调用是在 POSTROUTING点之后了,也可以理解为直接通过调用二层的发送函数,将三层构造的数据包发送出去。该函数实际上会调用 skb->dev->hard_start_xmit,即对应网卡的驱动函数,将数据包直接发送的出去。
很显然,这个工作在二层的函数,发送数据包(数据包在二层的时候准确叫法应该是帧,我们这里是在三层直接调用的,权且还称作数据包)的方式是不需要再查路由了。
但是,二层发送的时候是需要根据目的MAC来进行的。在第一种方法构造的数据包中,仅仅交换了IP地址,而没有对MAC做任何修改。这样直接调用 dev_queue_xmit是会产生问题的,并且该函数发送的内容应该是从二层头部开始,到数据包的结束。因此,如果三层构造的数据包,想调用该函数直接发送数据包的话,则需要修改数据包的源和目的MAC,并将skb->data指针指向MAC头部,以及skb->len的值也要加上头部的长度方法。以下是可参考的示例代码:
- unsigned char mac_temp[ETH_ALEN] = {0};
- struct ethhdr *mach = NULL;
- ……
- /*code…… 构造数据包的IP即上层协议及数据*/
- ……
- /*交换源和目的MAC*/
- mach = (struct ethhdr *)skb->mac.raw;
- memcpy(mac_temp, (unsigned char *)mach->h_dest, ETH_ALEN);
- memcpy(mach->h_dest, (unsigned char *)mach->h_source, ETH_ALEN);
- memcpy(mach->h_source, mac_temp, ETH_ALEN);
- /*修改skb->data指针,使其指向MAC头部,并且增加skb->len*/
- skb_push(skb , ETH_HLEN);
- /*直接调用该函数,将数据包从网卡上发送出去*/
- ret = dev_queue_xmit(skb);
unsigned char mac_temp[ETH_ALEN] = {0}; struct ethhdr *mach = NULL; …… /*code…… 构造数据包的IP即上层协议及数据*/ …… /*交换源和目的MAC*/ mach = (struct ethhdr *)skb->mac.raw; memcpy(mac_temp, (unsigned char *)mach->h_dest, ETH_ALEN); memcpy(mach->h_dest, (unsigned char *)mach->h_source, ETH_ALEN); memcpy(mach->h_source, mac_temp, ETH_ALEN); /*修改skb->data指针,使其指向MAC头部,并且增加skb->len*/ skb_push(skb , ETH_HLEN); /*直接调用该函数,将数据包从网卡上发送出去*/ ret = dev_queue_xmit(skb);这里还要顺便说一下构造的数据包发送完毕之后,对于hook函数的返回值问题。
(1)第一种发送数据包的实现,对于send_reset函数的实现中,由于单独申请了nskb的内存,并构造的新的数据包。新数据包接着走NF的流程了。而对于原始的skb,就通过模块的返回值return NF_DROP做出了处理。
(2)第二种发送数据包的实现,若是基于已有数据包的基础上重新构造的数据包,那么实际上原始数据包的内容已经不复存在,而且调用完毕 dev_queue_xmit已将同一块缓冲区,只是填充了新数据的数据包发送出去,因此,这里已经没有原始数据包的存在了,需要返回 NF_STOLEN,告诉协议栈不用关心原始的包即可。否则,若是新数据包是单独申请的内存,那么对于原数据包还应该是返回NF_DROP.