因为近期工作需要分析终端发送过来的数据包,于是便在社区了解了Netfilter框架,经过总结,有了以下的文章。由于个人写代码时间不长,首先未考虑到代码的健壮性,其次也是给刚刚开始了解Netfilter框架的同学一些参考。
内容的不足之处请各位大佬多多指教。
1、什么是Netfilter?
Netfilter是Linux 2.4.x引入的一个子系统,它作为一个通用的、抽象的框架,提供一整套的hook函数的管理机制,使得诸如数据包过滤、网络地址转换(NAT)和基于协议类型的连接跟踪成为了可能,翻译成小白能听懂的话来说,就是Netfilter框架提供了主要的几个接口。当本机发送/接受数据包的时候,可以调用Netfilter上的接口(hook)对数据进行我们想要的处理。
1.1 Netfilter的hook函数是如何调用的(本文基于内核版本号为:4.15,想知道自己的内核版本号,在终端下输入:uname -r)
1.1.1Netfilter的5个hook点,分别为:
[1]:NF_IP_PRE_ROUTING:刚刚进入网络层的数据包通过此点(刚刚进行完版本号,校验和等检测), 目的地址转换在此点进行;
[2]:NF_IP_LOCAL_IN:经路由查找后,送往本机的通过此检查点,INPUT包过滤在此点进行;
[3]:NF_IP_FORWARD:要转发的包通过此检测点,FORWARD包过滤在此点进行;
[4]:NF_IP_POST_ROUTING:所有马上便要通过网络设备出去的包通过此检测点,内置的源地址转换功能(包括地址伪装)在此点进行;
[5]:NF_IP_LOCAL_OUT:本机进程发出的包通过此检测点,OUTPUT包过滤在此点进行。
1.1.2了解完这些hook点我们就需要知道如何在hook点注册/解注我们自己的hook。
**首先是hook函数的信息:
static tatic struct nf_test_ops[] nfho = {
.hook = my_func, //我们自己要在hook上做操作的函数的名字
.pf = PF_INET,//协议簇,PF_INET表示IPV4
.hooknum =NF_INET_LOCAL_IN , //hook挂载点,本次数据包解析就是在该hook点上进行
.priority = NF_IP_PRI_FIRST, //hook函数的优先级
};
**其次是hook函数的注册。
static int __init init_nf_test(void) {
int ret;
ret = nf_register_net_hooks(&init_net,nf_test_ops, ARRAY_SIZE(nf_test_ops));
if (ret < 0) {
printk("register hook fail\n"); //如果注册失败就在终端输出注册register hook fail
return ret;
}
return 0;
}
**最后是hook函数的解注。
static void __exit exit_nf_test(void) {
nf_unregister_net_hooks(&init_net,nf_test_ops, ARRAY_SIZE(nf_test_ops));
}
2、如何对IP数据进行处理
在本机接受或发送数据包的时候,所有的数据可以理解为全部打包在skb这个结构体内,我们对IP的分析就是操作函数在skb结构体内内进行指针偏移。本文不详解skb结构体,具体可参见:https://blog.csdn.net/Hbw_aini/article/details/125436745?spm=1001.2014.3001.5506
在iphdr结构中主要有以下几个参数:
struct iphdr {
#if defined(__LITTLE_ENDIAN_BITFIELD)
__u8ihl:4,//首部长度
version:4;//版本号
#else
#error"Please fix <asm/byteorder.h>"
#endif
__u8tos;//服务类型
__be16tot_len;//总长度
__be16id;//标识
__be16frag_off;//分片偏移
__u8ttl;//生存时间
__u8protocol;//协议
__sum16check;//校验和
__be32saddr;//源ip地址
__be32daddr;//目的ip地址
/*The options start here. */
};
3、核心代码
unsigned int ipt_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 = ip_hdr(skb);//用ip_hdr()函数来将skb结构体内的ip数据交给iphdr
struct ethhdr *eth_header;
char *data;
eth_header = (struct ethhdr *)(skb_mac_header(skb));
iph = (struct iphdr *)(skb_network_header(skb));
hdr_dump(eth_header);
printk("src IP:'"NIPQUAD_FMT"', dst IP:'"NIPQUAD_FMT"' \n",
NIPQUAD(iph->saddr), NIPQUAD(iph->daddr));
//以下是对ip的各个参数进行的分析并打印
printk("version:0x%02x ihl:0x%02x (%u) tos:0x%02x tot_len:0x%02x (%u) id:0x%02x (%u) ",iph->version,iph->ihl,iph->tos,iph->tot_len,iph->id);
printk("frag_off:0x%02x (%u) ttl:0x%02x (%u) protocol:0x%02x (%u) check:0x%04x ",iph->frag_off,iph->ttl,iph->protocol,iph->check);
return NF_ACCEPT;
}
4、全部代码
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/skbuff.h>
#include <linux/ip.h>
#include <linux/udp.h>
#include <linux/tcp.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
MODULE_LICENSE("GPLv3");
unsigned int ipt_hook(unsigned int hooknum, struct sk_buff *skb,
const struct net_device *in, const struct net_device *out,
int (*okfn)(struct sk_buff *));
//hook函数定义
static struct nf_hook_ops nf_test_ops[] = {
{
.hook = (nf_hookfn *)ipt_hook,
.pf = NFPROTO_IPV4,
.hooknum = NF_IP_INET_LOCAL_IN,
.priority = NF_IP_PRI_FIRST,
},
};
//输出网络头
void hdr_dump(struct ethhdr *ehdr) {
printk("[MAC_DES:%x,%x,%x,%x,%x,%x "
"MAC_SRC: %x,%x,%x,%x,%x,%x Prot:%x]\n",
ehdr->h_dest[0],ehdr->h_dest[1],ehdr->h_dest[2],ehdr->h_dest[3],
ehdr->h_dest[4],ehdr->h_dest[5],ehdr->h_source[0],ehdr->h_source[1],
ehdr->h_source[2],ehdr->h_source[3],ehdr->h_source[4],
ehdr->h_source[5],ehdr->h_proto);
}
//将ip地址转换为点分十进制,即:0.0.0.0的格式
#define NIPQUAD(addr) \
((unsigned char *)&addr)[0], \
((unsigned char *)&addr)[1], \
((unsigned char *)&addr)[2], \
((unsigned char *)&addr)[3]
#define NIPQUAD_FMT "%u.%u.%u.%u"
unsigned int ipt_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 = ip_hdr(skb);
struct ethhdr *eth_header;
char *data;
eth_header = (struct ethhdr *)(skb_mac_header(skb));
iph = (struct iphdr *)(skb_network_header(skb));
hdr_dump(eth_header);
printk("src IP:'"NIPQUAD_FMT"', dst IP:'"NIPQUAD_FMT"' \n",
NIPQUAD(iph->saddr), NIPQUAD(iph->daddr));
printk("version:0x%02x ihl:0x%02x (%u) tos:0x%02x tot_len:0x%02x (%u) id:0x%02x (%u) ",iph->version,iph->ihl,iph->tos,iph->tot_len,iph->id);
printk("frag_off:0x%02x (%u) ttl:0x%02x (%u) protocol:0x%02x (%u) check:0x%04x ",iph->frag_off,iph->ttl,iph->protocol,iph->check);
return NF_ACCEPT;
}
static int __init init_nf_test(void) {
int ret;
ret = nf_register_net_hooks(&init_net,nf_test_ops, ARRAY_SIZE(nf_test_ops));
if (ret < 0) {
printk("register nf hook fail\n");
return ret;
}
return 0;
}
static void __exit exit_nf_test(void) {
nf_unregister_net_hooks(&init_net,nf_test_ops, ARRAY_SIZE(nf_test_ops));
}
module_init(init_nf_test);
module_exit(exit_nf_test);
Makefile:
obj-m += nf_test.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
test:
sudo dmesg -C
sudo insmod example.ko
sudo rmmod example.ko
dmesg