转发:http://blog.csdn.net/stonesharp/article/details/27091391

数据包在内核态得捕获、修改和转发(基于 netfilter)
    忙活了好几天,经过多次得死机和重启,终于把截获的数据包转发的功能给实现了。同时,也吧sk_buff结构学习了一下。
    本程序利用netfilter的钩子函数在PREROUTING处捕获数据包,并且修改数据包首部信息,之后直接转发,从而实现对数据包转发得功能。修改数据包得数据和地址之后,最主要的就是对tcp或dp校验和得计算,内核中有相应得函数,但是调用时要明白各个参数所代表得含义。在本程序中,为了验证对 skb->data指针的理解,本人还试着对截获的数据包进行了push和pull得调用。现拿出来与大家分享。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/netfilter.h>
#include <linux/socket.h>/*PF_INET*/
#include <linux/netfilter_ipv4.h>/*NF_IP_PRE_FIRST*/
#include <linux/skbuff.h>
#include <linux/netdevice.h>
#include <linux/inet.h> /*in_aton()*/
#include <net/ip.h>
#include <net/tcp.h> #define ETHALEN 14 MODULE_LICENSE("GPL");
MODULE_AUTHOR("bbo"); static struct nf_hook_ops nfho; unsigned int checksum(unsigned int hooknum,
struct sk_buff *__skb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff *))
{
struct sk_buff *skb;
struct net_device *dev;
struct iphdr *iph;
struct tcphdr *tcph;
int tot_len;
int iph_len;
int tcph_len;
int ret; skb = __skb;
if (skb == NULL)
return NF_ACCEPT; iph = ip_hdr(skb);
if (iph == NULL)
return NF_ACCEPT; tot_len = ntohs(iph->tot_len); if (iph->daddr == in_aton("173.26.100.224"))
{
iph_len = ip_hdrlen(skb);/*in ip.h*/
skb_pull(skb, iph_len); //skb->data指针定位到了传输层 skb_reset_transport_header(skb);/*重置首部长度,现在的首部长度包括了的ip首部长度*/
if (iph->protocol == IPPROTO_TCP)
{
tcph = tcp_hdr(skb);
tcph_len = tcp_hdrlen(skb);
if (tcph->dest == htons(3306)) //根据自己得需求来进行过滤数据包
{
iph->saddr = in_aton("1.2.3.4");
dev = dev_get_by_name(&init_net, "eth0"); tcph->check = 0;
skb->csum = csum_partial((unsigned char *)tcph, tot_len - iph_len, 0);
tcph->check = csum_tcpudp_magic(iph->saddr,
iph->daddr,
ntohs(iph->tot_len) - iph_len, iph->protocol,
skb->csum);
iph->check = 0;
iph->check = ip_fast_csum(iph, iph->ihl); skb->ip_summed = CHECKSUM_NONE;
skb->pkt_type = PACKET_OTHERHOST;
skb->dev = dev;
skb_push(skb, iph_len); /*在返回之前,先将skb中得信息恢复至原始L3层状态*/
//skb_reset_transport_header(skb); skb_push(skb, ETHALEN);//将skb->data指向l2层,之后将数据包通过 dev_queue_xmit()发出 ret = dev_queue_xmit(skb);
if (ret < 0)
{
printk("dev_queue_xmit() error\n");
goto out;
}
return NF_STOLEN;
}
}
skb_push(skb, iph_len); /*在返回之前,先将skb中得信息恢复至原始L3层状态*/
skb_reset_transport_header(skb);
} return NF_ACCEPT;
out:
dev_put(dev);
//free(skb); return NF_DROP;
} static int __init filter_init(void)
{
int ret;
nfho.hook = checksum;
nfho.pf = AF_INET;
nfho.hooknum = NF_INET_PRE_ROUTING;
nfho.priority = NF_IP_PRI_FIRST; ret = nf_register_hook(&nfho);
if (ret < 0)
{
printk("%s\n", "can't modify skb hook!");
return ret;
} return 0;
} static void filter_fini(void)
{
nf_unregister_hook(&nfho);
} module_init(filter_init);
module_exit(filter_fini);

  

module_init(filter_init);
module_exit(filter_fini);
    本程序是利用了截获得数据包进行得实验,由于数据包中得有原始mac,所以未对数据包得mac进行修改操作。其实,在调用dev_queue_xmit(skb)函数前,大家应该构造得是一个完整得skb,即应该根据需要来对mac进行修改,不过,不需要对L2层进行校验计算。大家如果需要就根据此程序进行修改吧,不算麻烦。
   写程序时我还有一点不明白,就是计算tcp得校验和时

tcph->check = 0;
                skb->csum = csum_partial((unsigned char *)tcph, tot_len - iph_len,0);
                tcph->check = csum_tcpudp_magic(iph->saddr,
                        iph->daddr,
                        ntohs(iph->tot_len) - iph_len,iph->protocol,
                        skb->csum)

这样写,正确!

而另一种计算方式却时错误,如下。如果大家有明白原因的请指点,谢谢!

skb->csum = csum_partial((unsigned char *)(tcph + tcph_len) , tot_len - iph_len - tcph_len,0);

tcph->check = 0;
                tcph->check = csum_tcpudp_magic(iph->saddr,
                        iph->daddr,
                        ntohs(iph->tot_len) - iph_len,iph->protocol,
                        csum_partial((unsigned char *)tcph,tcph_len, skb->csum));

这样写,错误!

一、构造数据包简析

这里并不详细介绍如何在内核中构造数据包,下文如有需要会在适当的位置进行分析。这里简单的分析讲一下内核态基于Netfilter框架构造数据包的方式。

内核中可以用到的构造数据包的方式,个人认为可以分为两种。

其一,我们直接用alloc_skb申请一个skb结构体,然后根据实际的应用填充不同的成员,或者基于当前数据包的skb,调用skb_copy_expand()函数等新申请一个nskb,并且拷贝skb的内容。

其二,也是个人比较常用的,就是直接在先前接收到的数据包skb上作修改,主要有源IP、目IP,如果是TCP/UDP协议的话,还有源端口目的端口号。总之,就是根据自己的需求去调整数据包的相关成员即可。

通常,这两种方式最终可能都要涉及到重新计算各个部分的校验和,这也是必须的。

二、如何发送构造的数据包

承接上文,数据包已经构造完毕,下一步关键就是如何发送数据包了。个人这里总结的有两种方法。

方法一,就是让数据包接着按照Netfilter的流程进行传输。因为数据包的一些内容已经被更改,尤其是当源IP和目的IP被更改,主要是交换的情况下,是需要确保有路由可查的。

NF框架中查路由的位置一是在PREROUTING之后,而是在LOCALOUT之后。又由于这里是需要将数据包从本地发送出去。因此,可以考虑让修改后的数据包从LOCALOUT点发出。

内核代码中有这种方式的典型体现。本文涉及的相关内核代码的版本都是2.6.18.3。源文件为ipt_REJECT.c,函数send_reset用于往当前接收到数据包的源IP上发送RST包,整个函数涉及了数据包的构造和发送,这里一起做个简单分析。

  1. /* Send RST reply */
  2. static void send_reset(struct sk_buff *oldskb, int hook)
  3. {
  4. struct sk_buff *nskb;
  5. struct iphdr *iph = oldskb->nh.iph;
  6. struct tcphdr _otcph, *oth, *tcph;
  7. struct rtable *rt;
  8. u_int16_t tmp_port;
  9. u_int32_t tmp_addr;
  10. int needs_ack;
  11. int hh_len;
  12. /* 判断是否是分片包*/
  13. if (oldskb->nh.iph->frag_off & htons(IP_OFFSET))
  14. return;
  15. /*得到TCP头部指针*/
  16. oth = skb_header_pointer(oldskb, oldskb->nh.iph->ihl * 4,
  17. sizeof(_otcph), &_otcph);
  18. if (oth == NULL)
  19. return;
  20. /* 当期收到的包就是RST包,就不用再发送RST包了*/
  21. if (oth->rst)
  22. return;
  23. /*检查数据包的校验和是否正确*/
  24. if (nf_ip_checksum(oldskb, hook, iph->ihl * 4, IPPROTO_TCP))
  25. return;
  26. /*这一步比较关键,做的就是更新路由的工作。该函数的主要工作就是将当前数据包的源IP当做路由的目的IP,同时考虑数据包的目的IP,得到去往该源IP的路由*/
  27. if ((rt = route_reverse(oldskb, oth, hook)) == NULL)
  28. return;
  29. hh_len = LL_RESERVED_SPACE(rt->u.dst.dev);
  30. /* 拷贝当前的oldskb,包括skb结构体和数据部分。这就是我们上面提到的构造数据包的第一种方式*/
  31. nskb = skb_copy_expand(oldskb, hh_len, skb_tailroom(oldskb),
  32. GFP_ATOMIC);
  33. if (!nskb) {
  34. dst_release(&rt->u.dst);
  35. return;
  36. }
  37. /*因为是拷贝的oldskb,这里不需要再引用了,因此释放对该路由项的引用*/
  38. dst_release(nskb->dst);
  39. /*将新构造数据包引用的路由指向上面由route_reverse函数返回的新的路由项 */
  40. nskb->dst = &rt->u.dst;
  41. /* 清除nskb中拷贝过来的oldskb中链接跟踪相关的内容*/
  42. nf_reset(nskb);
  43. nskb->nfmark = 0;
  44. skb_init_secmark(nskb);
  45. /*以下就是构造数据包的实际数据部分。如果我们将这里不为nskb新申请缓冲区,而直接指向oldskb的缓冲区,就使我们上面提到的第二种构造数据包的方法。*/
  46. /*获取nskb的tcp header*/
  47. tcph = (struct tcphdr *)((u_int32_t*)nskb->nh.iph + nskb->nh.iph->ihl);
  48. /*交换源和目的IP */
  49. tmp_addr = nskb->nh.iph->saddr;
  50. nskb->nh.iph->saddr = nskb->nh.iph->daddr;
  51. nskb->nh.iph->daddr = tmp_addr;
  52. /*交换源和目的端口 */
  53. tmp_port = tcph->source;
  54. tcph->source = tcph->dest;
  55. tcph->dest = tmp_port;
  56. /*重置TCP头部的长度,并修改IP头部中记录的数据包的总长度。因为这里是发送RST报文,只需要有TCP的头部,不需要TCP的数据部分*/
  57. tcph->doff = sizeof(struct tcphdr)/4;
  58. skb_trim(nskb, nskb->nh.iph->ihl*4 + sizeof(struct tcphdr));
  59. nskb->nh.iph->tot_len = htons(nskb->len);
  60. /*重新设置 seq, ack_seq,分两种情况(TCP/IP详解有描述)*/
  61. if (tcph->ack) { /*原始数据包中ACK标记位置位的情况*/
  62. needs_ack = 0;
  63. tcph->seq = oth->ack_seq; /*原始数据包的ack_seq作为nskb的seq*/
  64. tcph->ack_seq = 0;
  65. } else { /*原始数据包中ACK标记位没有置位的情况,初始连接SYN或者结束连接FIN等*/
  66. needs_ack = 1;
  67. /*这种情况应该是SYN或者FIN包,由于SYN和FIN包都占用1个字节的长度。因此ack_seq应该等于旧包的seq+1即可。这里之所以这样表示,可能是还存在其他情况的数据包。*/
  68. tcph->ack_seq = htonl(ntohl(oth->seq) + oth->syn + oth->fin
  69. + oldskb->len - oldskb->nh.iph->ihl*4
  70. - (oth->doff<<2));
  71. tcph->seq = 0;
  72. }
  73. /* RST标记位置1*/
  74. ((u_int8_t *)tcph)[13] = 0;
  75. tcph->rst = 1;
  76. tcph->ack = needs_ack;
  77. tcph->window = 0;
  78. tcph->urg_ptr = 0;
  79. /*重新计算TCP校验和*/
  80. tcph->check = 0;
  81. tcph->check = tcp_v4_check(tcph, sizeof(struct tcphdr),
  82. nskb->nh.iph->saddr,
  83. nskb->nh.iph->daddr,
  84. csum_partial((char *)tcph,
  85. sizeof(struct tcphdr), 0));
  86. /* 修改IP包的TTL,并且设置禁止分片*/
  87. nskb->nh.iph->ttl = dst_metric(nskb->dst, RTAX_HOPLIMIT);
  88. /* Set DF, id = 0 */
  89. nskb->nh.iph->frag_off = htons(IP_DF);
  90. nskb->nh.iph->id = 0;
  91. /*重新计算IP数据包头部校验和*/
  92. nskb->nh.iph->check = 0;
  93. nskb->nh.iph->check = ip_fast_csum((unsigned char *)nskb->nh.iph,
  94. nskb->nh.iph->ihl);
  95. /* "Never happens" */
  96. if (nskb->len > dst_mtu(nskb->dst))
  97. goto free_nskb;
  98. /*使nskb和oldskb的链接记录关联*/
  99. nf_ct_attach(nskb, oldskb);
  100. /*这里就是最终发送数据包的方式,具体方法就是让新数据包经过LOACLOUT的hook点,然后查路由,最后经由PREROUTING点,将数据包发送出去。
  101. 其实这里我还是有1个疑问:(1)为什么不可以直接查找路由,而必须先经过LOCALOUT点;*/
  102. NF_HOOK(PF_INET, NF_IP_LOCAL_OUT, nskb, NULL, nskb->dst->dev,
  103. dst_output);
  104. return;
  105. free_nskb:
  106. kfree_skb(nskb);
  107. }

其实,这不是丢到了高层,而是和ip_queue_xmit()发送过程意义一样。 
对这包进行重新路由后,封装了头部,之后,放到了NF_IP_LOCAL_IN之前而已。

其实,这里面只要修改了中途修改了ip地址,肯定是需要手动重新路由的。 
这就涉及到一些比较复杂的route cache的查找,如果没有就去查找route tables;之后,进行路由结构和neighbour结构的关联,就涉及到邻居子系统的相关操作;接着就涉及到arp cache的查找,如果没有,进行一些操作,arp的过程等等,才找到了相关的ip对应的mac信息。

netfilter的钩子——数据包在内核态得捕获、修改和转发的更多相关文章

  1. 数据包从物理网卡流经 Open vSwitch 进入 OpenStack 云主机的流程

    目录 文章目录 目录 前言 数据包从物理网卡进入虚拟机的流程 物理网卡处理 如何将网卡收到的数据写入到内核内存? 中断下半部分软中断处理 数据包在内核态 OvS Bridge(Datapath)中的处 ...

  2. linux 内核网络数据包接收流程

    转:https://segmentfault.com/a/1190000008836467 本文将介绍在Linux系统中,数据包是如何一步一步从网卡传到进程手中的. 如果英文没有问题,强烈建议阅读后面 ...

  3. Linux数据包路由原理、Iptables/netfilter入门学习

    相关学习资料 https://www.frozentux.net/iptables-tutorial/cn/iptables-tutorial-cn-1.1.19.html http://zh.wik ...

  4. Linux网络 - 数据包的接收过程【转】

    转自:https://segmentfault.com/a/1190000008836467 本文将介绍在Linux系统中,数据包是如何一步一步从网卡传到进程手中的. 如果英文没有问题,强烈建议阅读后 ...

  5. Linux 中的网络数据包捕获

    Linux 中的网络数据包捕获 Ashish Chaurasia, 工程师 简介: 本教程介绍了捕获和操纵数据包的不同机制.安全应用程序,如 VPN.防火墙和嗅探器,以及网络应用程序,如路由程序,都依 ...

  6. [转]Linux网络 - 数据包的接收过程

    转, 原文: https://segmentfault.com/a/1190000008836467 ------------------------------------------------- ...

  7. Windows下底层数据包发送实战

    1.简介 所谓“底层数据包”指的是在“运行”于数据链路层的数据包,简单的说就是“以太网帧”,而我们常用的Socket只能发送“运行”在传输层的TCP.UDP等包,这些传输层数据包已经能满足绝大部分需求 ...

  8. dpdk数据包捕获技术笔记1

    1 高效捕包技术的重要性 高性能系统需要在很短的时间内,成功的收集和处理大量的数据,目标系统的实时数据需要被收集,管里和控制. 2 传统的数据包捕获机制 Inter指出,影响数据包捕获性能主要原因是系 ...

  9. (转)linux用户态和内核态理解

    原文:https://blog.csdn.net/buptapple/article/details/21454167 Linux探秘之用户态与内核态-----------https://www.cn ...

随机推荐

  1. plsql配置

    1.安装客户的plsql,安装完成应该有PLSQL Developer和instantclient_11_2两个文件夹 2.打开plsql,取消登录直接进入主界面,通过 TOOLS->PREFE ...

  2. 优化php代码 - 字符串echo输出 逗号也可作php连接符

    2016年12月12日10:00:16 ====================== 网页访问速度的提升,是可以通过代码的优化来实现的.代码的优化,并不是说代码越少越好,而是主要看代码的运行能力和执行 ...

  3. Android开发笔记之《JNI常用知识汇总》

    参考资料: Android Studio中NDK开发 : http://www.tuicool.com/articles/NBjQnyAndroid Studio使用新的Gradle构建工具配置NDK ...

  4. No module named migrate.versioning

    在学习mega-tutorial的数据库章节时创建数据库遇到了问题,在stackoverflow上找到了结果 pip install sqlalchemy==0.7.9 pip install sql ...

  5. 如何在网页中添加“QQ交流”

    今天在撸码时,想到这个问题,有些网页中会有诸如,那么如何在网页添加"QQ交谈"? 第一步.登录QQ: 第二步.打开网页:QQ推广,启用QQ通讯组件: 第三步.选择组件样式,设置提示 ...

  6. 你想的到想不到的 javascript 应用小技巧方法

    javascript 在前端应用体验小技巧继续积累. 事件源对象 event.srcElement.tagName event.srcElement.type 捕获释放 event.srcElemen ...

  7. tyvj1294 小v的舞会

    背景 "梦中伊人,断我男儿几寸柔肠,于断桥,不知西风自憔悴那姑娘."小v的梦中伊人要带领一大帮姐妹MM们来小v家举办舞会,然而怎么安排跳舞的顺序成了大问题,你能帮他么? 描述 有n ...

  8. [Python] Python中的一些特殊函数

    1. 过滤函数filter 定义:filter 函数的功能相当于过滤器.调用一个布尔函数bool_func来迭代遍历每个列表中的元素:返回一个使bool_func返回值为true的元素的序列. a=[ ...

  9. ThinkPHP2.2框架执行流程图,ThinkPHP控制器的执行流程

    ThinkPHP2.2框架执行原理.流程图在线手册 ThinkPHP控制器的执行流程 对用户的第一次URL访问 http://<serverIp>/My/index.php/Index/s ...

  10. 【总结】虚拟机VirtualBox各种使用技巧

    作为个人学习研究,VirtualBox是首选,它是Oracle下免费的.开源.跨平台的一款虚拟机软件,小巧.实用,一点也不逊于商业版的VMware Workstation. VirtualBox官网: ...