上篇博客主要介绍了内核抓包设计思路与效果,并没有给出详细的设计实现,看起来就像一个花架子,华而不实。本篇博客就结合具体的代码介绍一下抓包的实现过程。先大致概括一下代码的思路,抓包模块启动后,就去netfilter上面注册两个钩子函数,分别放在PRE_ROUTING链和POSTROUTING链上,抓取进来的报文和出去的报文。当报文进入到netfilter层面处理时,钩子函数对报文做一下简单的匹配判断,符合条件就复制一份发送出来。先看代码:

/**  Description : 内核抓包模块demo,内核版本3.4.39*  Date        : 20180701*  Author      : fuyuande*/#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/socket.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
#include <linux/netfilter_arp.h>
#include <linux/in.h>
#include <linux/if_ether.h>
#include <linux/if_arp.h>
#include <linux/ip.h>
#include <net/ip.h>
#include <linux/skbuff.h>
#include <linux/inet.h>#include "capture_demo.h"struct dst_entry *output_dst = NULL; //出口设备指针//查询报文源端口或者目的端口
unsigned short capture_get_port(const struct sk_buff *skb,int dir)
{struct iphdr  *iph  = NULL;struct tcphdr *tcph = NULL;struct udphdr *udph = NULL;unsigned short port = 0;iph = ip_hdr(skb);if(!iph){log_warn("ip header null \r\n");return 0;}if(iph->protocol == IPPROTO_TCP){tcph = tcp_hdr(skb);if(!tcph){log_warn("tcp header null \r\n");return 0;}if(dir == 0){port = ntohs(tcph->dest); tcph = NULL;return port;}else{        port = ntohs(tcph->source);tcph = NULL;return port;}}else if(iph->protocol == IPPROTO_UDP){udph = udp_hdr(skb);if(!udph){log_warn("udp header null \r\n");return 0;}if(dir == 0){port = ntohs(udph->dest);udph = NULL;return port;}else{port = ntohs(udph->source);udph = NULL;return port;}}elsereturn 0;
}//查询传输层协议 TCP/UDP/ICMP
unsigned int capture_get_transport_protocol(const struct sk_buff *skb){struct iphdr *iph = NULL;iph = ip_hdr(skb);if(!iph)return 0;if(iph->protocol == IPPROTO_TCP)return (CAPTURE_TCP);if(iph->protocol == IPPROTO_UDP)return (CAPTURE_UDP);return 0;
}//复制报文并添加新的头域发送到指定的接收地址
int capture_send(const struct sk_buff *skb, int output)
{struct ethhdr  *oldethh = NULL;struct iphdr   *oldiph  = NULL;struct iphdr   *newiph  = NULL;struct udphdr  *newudph = NULL; struct sk_buff *skb_cp  = NULL;struct net *net = NULL;unsigned int headlen = 0;headlen = 60;    // mac + ip + udp = 14 + 20 + 8 = 42, 这里分配大一点//如果报文头部不够大,在复制的时候顺便扩展一下头部空间,够大的话直接复制if(skb_headroom(skb) < headlen){skb_cp = skb_copy_expand(skb,headlen,0,GFP_ATOMIC);            if(!skb_cp){log_warn(" realloc skb fail \r\n");return -1;}}else{skb_cp = skb_copy(skb, GFP_ATOMIC);if(!skb_cp){log_warn(" copy skb fail \r\n");return -1;}}oldiph = ip_hdr(skb);if(!oldiph){log_warn("ip header null \r\n");kfree_skb(skb_cp);return -1;}/** 抓包报文格式---------------------------------------------------------------------| new mac | new ip | new udp | old mac | old ip| old tcp/udp | data |---------------------------------------------------------------------|        new header          |            new data                  |            ---------------------------------------------------------------------    *///如果是出去的报文,因为是在IP层捕获,MAC层尚未填充,这里将MAC端置零,并填写协议字段if(output){skb_push(skb_cp,sizeof(struct ethhdr));skb_reset_mac_header(skb_cp);oldethh = eth_hdr(skb_cp);oldethh->h_proto = htons(ETH_P_IP);            memset(oldethh->h_source,0,ETH_ALEN); memset(oldethh->h_dest,0,ETH_ALEN);if(skb_cp->dev != NULL)memcpy(oldethh->h_source,skb_cp->dev->dev_addr,ETH_ALEN);                                                                   }else{//如果是进来的报文,MAC层已经存在,不做任何处理,直接封装skb_push(skb_cp,sizeof(struct ethhdr));skb_reset_mac_header(skb_cp);oldethh = eth_hdr(skb_cp);oldethh->h_proto = htons(ETH_P_IP);            }//添加IP, UDP头部skb_push(skb_cp, sizeof(struct iphdr) + sizeof(struct udphdr));    skb_reset_network_header(skb_cp);skb_set_transport_header(skb_cp,sizeof(struct iphdr));newiph = ip_hdr(skb_cp);newudph = udp_hdr(skb_cp);if((newiph == NULL) || (newudph == NULL)){log_warn("new ip udp header null \r\n");kfree_skb(skb_cp);return -1;}/* 抓包的报文发送的时候是调用协议栈函数发送的,所以output钩子函数会捕获到抓包报文,* 这里我们要把抓包报文和正常报文区分开,区分方式就是判断源端口,我们抓到的报文*  在送出去的时候填写的是保留端口0,如果钩子函数遇到这样的报文就会直接let go* 防止重复抓包,这一点在测试的时候很重要,一旦重复抓包,系统就直接挂了...*/memcpy((unsigned char*)newiph,(unsigned char*)oldiph,sizeof(struct iphdr));newudph->source = htons(0);newiph->saddr = in_aton("1.1.1.1");newudph->dest = htons(8080);            //抓包服务器端口newiph->daddr = in_aton("192.168.199.123"); //抓包服务器地址newiph->ihl = 5;newiph->protocol = IPPROTO_UDP;  newudph->len = htons(ntohs(oldiph->tot_len) + sizeof(struct udphdr) + sizeof(struct ethhdr));newiph->tot_len = htons(ntohs(newudph->len) + sizeof(struct iphdr));/* disable gso_segment */        skb_shinfo(skb_cp)->gso_size = htons(0);//计算校验和newudph->check = 0;newiph->check = 0;skb_cp->csum = 0;skb_cp->csum = csum_partial(skb_transport_header(skb_cp), htons(newudph->len), 0);    newudph->check = csum_tcpudp_magic(newiph->saddr, newiph->daddr, htons(newudph->len), IPPROTO_UDP, skb_cp->csum);  skb_cp->ip_summed = CHECKSUM_NONE;if (0 == newudph->check){newudph->check = CSUM_MANGLED_0;}newiph->check = ip_fast_csum((unsigned char*)newiph, newiph->ihl);//设置出口设备if(skb_dst(skb_cp) == NULL){if(output_dst == NULL){kfree_skb(skb_cp);return -1;}else{dst_hold(output_dst);        skb_dst_set(skb_cp, output_dst);}}//路由查找if(ip_route_me_harder(skb_cp, RTN_UNSPEC)){kfree_skb(skb_cp);log_info("ip route failed \r\n");return -1;}//发送ip_local_out(skb_cp);return 0;
}//输入钩子函数
static unsigned int capture_input_hook(unsigned int hooknum,struct sk_buff *skb,const struct net_device *in,const struct net_device *out,int (*okfn)(struct sk_buff *))
{struct iphdr *iph = NULL;unsigned short sport = 0;iph = ip_hdr(skb);if(unlikely(!iph))return NF_ACCEPT;//只处理TCP和UDPif(iph->protocol != IPPROTO_TCP && iph->protocol != IPPROTO_UDP)return NF_ACCEPT;    //源地址和目的地址相同,只抓一次,在output钩子上处理一遍就够了if(iph->saddr == iph->daddr)return NF_ACCEPT;//设置传输层首部指针    skb_set_transport_header(skb, (iph->ihl*4));            //检查端口,端口为0的let gosport = capture_get_port(skb,1);if(sport == 0)return NF_ACCEPT;//复制一份报文并发送出去    capture_send(skb, 0);//返回accept,让系统正常处理return NF_ACCEPT;
}//输出钩子函数
static unsigned int capture_output_hook(unsigned int hooknum,struct sk_buff *skb,const struct net_device *in,const struct net_device *out,int (*okfn)(struct sk_buff *))
{struct iphdr *iph;unsigned short sport = 0;       iph = ip_hdr(skb);if(unlikely(!iph))return NF_ACCEPT;//只处理TCP或UDP        if(iph->protocol != IPPROTO_TCP && iph->protocol != IPPROTO_UDP)return NF_ACCEPT;//如果源端口为0,是抓包报文,直接let it go, 否则进行抓包sport = capture_get_port(skb,1); if(output_dst == NULL){if(skb_dst(skb) != NULL){output_dst = skb_dst(skb);dst_hold(output_dst); log_info("dst get success \r\n");          }}if(sport != 0)capture_send(skb, 1);return NF_ACCEPT;
}struct nf_hook_ops capture_hook_ops[] = {{.hook=capture_input_hook,       //输入钩子处理函数.pf=NFPROTO_IPV4,.hooknum=NF_INET_PRE_ROUTING,   //hook点.priority=NF_IP_PRI_FIRST + 10, //优先级},{.hook=capture_output_hook,      //输出钩子处理函数.pf=NFPROTO_IPV4,.hooknum=NF_INET_POST_ROUTING,  //hook点.priority=0,                    //优先级},    {}
};static int __init capture_init(void)
{        //注册钩子函数if(nf_register_hooks(capture_hook_ops,ARRAY_SIZE(capture_hook_ops))!=0){log_warn("netfilter register fail");return -1;}log_info("capture module init \r\n");return 0;
}static void __exit capture_exit(void)
{ //注销钩子函数nf_unregister_hooks(capture_hook_ops,ARRAY_SIZE(capture_hook_ops));   if(output_dst != NULL){dst_release(output_dst);log_info("dst release success \r\n");}    log_info("capture module exit \r\n");return ;
}module_init(capture_init)
module_exit(capture_exit)MODULE_ALIAS("capture");
MODULE_AUTHOR("fuyuande");
MODULE_DESCRIPTION("capture module");
MODULE_LICENSE("GPL");

代码里过滤条件很简单,就是TCP/UDP报文,当收到这样的报文就复制一份,添加新的头域发送到远端服务器上,这里需要指定远端服务器的端口和IP地址,至于源端口和目的端口,是可以任意填写的。我们调用的发送函数是ip_local_out(),这意味着抓包报文还会经过POST_ROUTING链,为了防止重复抓包,需要区分正常的报文和抓包报文,这里区分条件就是端口,抓包报文的源端口使用的是0,这样当收到这样的报文时候就直接accept处理。将报文发送出去的方式还有其它,例如dev_queue_xmit()接口,但是调用这个接口的前提是你已经知道了到远端服务器的出口设备dev以及下一条的mac地址,这样处理可能更快一点,不必再有协议栈处理一遍,不过呢,使用ip_local_out的一个优点就是协议栈会帮我们处理分片报文,如果抓到的报文过大的话,直接调用dev_queue_xmit有可能在发送途中被丢弃,而使用ip_local_out则IP协议栈会帮我们处理分片的事情,我们只需要调用这个接口就可以了。

看一下运行的实际效果图:

wireshark可以看到抓包模块送过来的报文,但是这样的报文并不能直接拿来分析,因为它外面还封装了mac, ip, udp头,需要去掉这些头部才能看到原始的报文,这就是抓包服务器的功能。今天先介绍到这,下一篇将抓包服务器的实现。

对了,代码我放到github上:

https://github.com/FuYuanDe/capture_demo.git

git clone下来直接make && insmod 就可以运行了。

またね!

linux 内核抓包功能实现基础(二) netfilter处理相关推荐

  1. linux 内核抓包功能实现基础(一)设计思路

    linux平台下面已经有了抓包工具tcpdump, 非常经典,使用起来也非常方便.但是因为某些系统架构上或者其它方面的原因,有时候tcpdump并不能满足产品实际需要,公司的产品是电信运营商相关的软硬 ...

  2. Linux 内核抓包功能实现基础(三) 抓包服务器的实现

    上回博客我们讲到了内核抓包内核端的实现,通过上篇博客的例子我们就能够开始抓包了,整个抓包的拓扑图如下: 当开始抓包后,抓包机器将抓到的报文送到服务器上,假设服务器地址是192.168.199.123: ...

  3. Linux 内核抓包功能实现基础(四) 手动查找邻居缓存填充MAC地址

    之前写了三篇关于内核抓包功能的实现,包括抓包原理.实现以及抓包服务器的实现.基本的功能都已经有了,但是还有些小问题有待解决.今天有空就解决一下. 开发版本基于内核3.4.39. 先介绍一下问题背景,抓 ...

  4. Linux 内核抓包功能实现基础(五) 常见问题解析

    之前在部门产品上开发了内核抓包模块,基于openwrt平台,通过netfilter框架实现相关功能.核心功能就是在netfilter 的PRE_ROUTING 和 POST_ROUTING链上增加两个 ...

  5. Linux内核--网络栈实现分析(二)--数据包的传递过程--转

    转载地址http://blog.csdn.net/yming0221/article/details/7492423 作者:闫明 本文分析基于Linux Kernel 1.2.13 注:标题中的&qu ...

  6. Linux tcpdump抓包

    简介 用简单的话来定义tcpdump,就是:dump the traffic on a network,根据使用者的定义对网络上的数据包进行截获的包分析工具. tcpdump可以将网络中传送的数据包的 ...

  7. Linux下抓包工具tcpdump详解

    简介 用简单的话来定义tcpdump,就是:dump the traffic on a network,根据使用者的定义对网络上的数据包进行截获的包分析工具. tcpdump可以将网络中传送的数据包的 ...

  8. centost查看网络信息_监控io性能、free命令、ps命令、查看网络状态、Linux下抓包...

    一.监控io性能 iostat命令 iostat命令被用于监视系统输入输出设备和cpu的使用情况.它的特点是汇报磁盘活动统计情况,同时也会汇报出cpu使用情况.通vmstat一样,iostat也有一个 ...

  9. Burpsuite工具的代理抓包功能实验

    实验声明:本实验教程仅供研究学习使用,请勿用于非法用途,违者一律自行承担所有风险! 实验目的 实验环境 实验原理 实验步骤 第一步 启动Burpsuite 第二步 配置Burpsuite的监听参数 第 ...

最新文章

  1. 微软开放 AI 系统工具 Counterfit 源代码
  2. javaScript 之 蚁人微任务
  3. 高通 8x12 添加 TP和按键
  4. ubun16.04+搜狗输入法
  5. -bash:/etc/profile Permission Denied
  6. python数据结构和算法3 栈、队列和排序
  7. 1010 一元多项式求导 (25分)
  8. linux11g导入10g 怎么改版本,Oracle 11g导入到10g引起的错误
  9. macos 全局代理 app_「主观向」macOS 好软推荐(使用体验)
  10. 渗透测试实践(工具使用总结)
  11. python 安装包国内源
  12. SPSS详细操作:生存资料的Cox回归分析
  13. deepin 输出搜狗输入法ctrl shift f快捷键
  14. 集合的基本运算及文氏图
  15. 计算机网络安全及故障谢辞,计算机网络安全初探.pdf
  16. The types of the interface org.apache.flink.util.OutputTag could not be inferred.
  17. 与rasp的初次心动
  18. 花落谁家---再见熊节
  19. 2db多少功率_db换算(db和功率的换算)
  20. 崔宝秋国际开源经验在小米开花 | 开源英雄

热门文章

  1. Spring.NET企业架构实践之 JQuery + FlexiGrid + ASP.NET MVC + NVelocity + WCF + LINQ + NHibernate 综合应用...
  2. [转]ASP.Net缓存总结
  3. sql 2005学习笔记1
  4. 离2006年考研还有一个月
  5. 3.9 训练一个 Softmax 分类器-深度学习第二课《改善深层神经网络》-Stanford吴恩达教授
  6. 3.7 感知器-机器学习笔记-斯坦福吴恩达教授
  7. STM32 基础系列教程 36 - Lwip_dns
  8. 把DXF导入到Altium Designer的完美方法
  9. 设计案例——点和圆的关系
  10. make: *** [out/target/common/obj/APPS/Phone_intermediates/classes-full-debug.jar] 错误 41