概述
Linux Netfilter技术(1)-概述已经介绍了Netfilter的基本概念和框架,主要是了解了Netfilter框架的Hook原理,这篇主要介绍如何基于Netfiter框架实现自己的hook函数。如果google或者百度大部分都是基于老接口的示例
nf_register_hook
,在kernel 4.13.x之后都是新接口nf_register_net_hook
,接下来主要是基于4.18的kernel讲解一下netfilter hook的例子。
hook结构体信息
为了注册一个钩子,需要填充nf_hook_ops结构体,包括钩子函数、设备、协议类型、钩子函数位置以及优先级,具体的定义如下:
struct nf_hook_ops {
/* User fills in from here down. */
nf_hookfn *hook;
struct net_device *dev;
void *priv;
u_int8_t pf;
unsigned int hooknum;
/* Hooks are ordered in ascending priority. */
int priority;
};
贴出来代码里的具体内容,一一说一下里面涉及到的内容
static const struct nf_hook_ops ipv4_test_in_ops = {
.hook = nf_ipv4_test_hook,
.pf = NFPROTO_IPV4,
.hooknum = NF_INET_LOCAL_IN,
.priority = NF_IP_PRI_FIRST,
};
.hook = nf_ipv4_test_hook
,钩子函数,它的函数参数如下:static unsigned int nf_ipv4_test_hook(void *priv, struct sk_buff *skb, const struct nf_hook_state *state);
我们主要使用skb这个参数来对数据流进程处理
.pf = NFPROTO_IPV4
,协议类型,除了IPV4还有如下几个:enum { NFPROTO_UNSPEC = 0, NFPROTO_INET = 1, NFPROTO_IPV4 = 2, NFPROTO_ARP = 3, NFPROTO_NETDEV = 5, NFPROTO_BRIDGE = 7, NFPROTO_IPV6 = 10, NFPROTO_DECNET = 12, NFPROTO_NUMPROTO, };
.hooknum = NF_INET_LOCAL_IN
钩子函数入口,这个参数代表我们实在Netfilter框架的什么位置调用该函数,还有其他一些的定义:enum nf_inet_hooks { NF_INET_PRE_ROUTING, NF_INET_LOCAL_IN, NF_INET_FORWARD, NF_INET_LOCAL_OUT, NF_INET_POST_ROUTING, NF_INET_NUMHOOKS };
.priority = NF_IP_PRI_FIRST
,因为同一个hook_num可以定义多个钩子函数,为了保证所有的有可以执行,所以有设置优先级,这个参数就是设置钩子函数的优先级的enum nf_ip_hook_priorities { NF_IP_PRI_FIRST = INT_MIN, NF_IP_PRI_RAW_BEFORE_DEFRAG = -450, NF_IP_PRI_CONNTRACK_DEFRAG = -400, NF_IP_PRI_RAW = -300, NF_IP_PRI_SELINUX_FIRST = -225, NF_IP_PRI_CONNTRACK = -200, NF_IP_PRI_MANGLE = -150, NF_IP_PRI_NAT_DST = -100, NF_IP_PRI_FILTER = 0, NF_IP_PRI_SECURITY = 50, NF_IP_PRI_NAT_SRC = 100, NF_IP_PRI_SELINUX_LAST = 225, NF_IP_PRI_CONNTRACK_HELPER = 300, NF_IP_PRI_CONNTRACK_CONFIRM = INT_MAX, NF_IP_PRI_LAST = INT_MAX, };
注册/卸载钩子函数
int nf_register_net_hooks(struct net *net, const struct nf_hook_ops *reg,
unsigned int n);
int nf_register_net_hook(struct net *net, const struct nf_hook_ops *reg);
void nf_unregister_net_hooks(struct net *net, const struct nf_hook_ops *reg,
unsigned int hookcount);
void nf_unregister_net_hook(struct net *net, const struct nf_hook_ops *reg);
具体的函数
注册:
static int __init init_nf_test(void) {
...
ret =nf_register_net_hook(&init_net, &ipv4_test_in_ops);
...
return 0;
}
里面的init_net
定义如下
extern struct net init_net;
卸载:
nf_unregister_net_hook(&init_net, &ipv4_test_in_ops);
代码示例
在 NF_INET_LOCAL_IN 处注册一个hook函数,打印包的m源和目的MAC地址和IP 地址
static unsigned int nf_ipv4_test_hook(void *priv,
struct sk_buff *skb,
const struct nf_hook_state *state)
{
struct ethhdr *eth_header;
struct iphdr *ip_header;
eth_header = (struct ethhdr *)(skb_mac_header(skb));
ip_header = (struct iphdr *)(skb_network_header(skb));
if('0' != eth_header->h_dest[0]){
hdr_dump(eth_header);
}
if('0' != ip_header->saddr){
printk("src IP:'"NIPQUAD_FMT"', dst IP:'"NIPQUAD_FMT"' \n",
NIPQUAD(ip_header->saddr), NIPQUAD(ip_header->daddr));
}
return NF_ACCEPT;
}
static int __init init_nf_test(void) {
int ret;
ret =nf_register_net_hook(&init_net, &ipv4_test_in_ops);
if (ret < 0) {
printk("register nf hook fail\n");
return ret;
}
printk(KERN_NOTICE "register nf test hook\n");
return 0;
}
static void __exit exit_nf_test(void) {
nf_unregister_net_hook(&init_net, &ipv4_test_in_ops);
printk(KERN_NOTICE "unregister nf test hook\n");
}
module_init(init_nf_test);
module_exit(exit_nf_test);
粘贴了部分代码,源码地址:
Netfliter_hook源码
实验结果:
[76291.212066] [MAC_DES:52,54,0,df,93,bf MAC_SRC: 52,54,0,11,e4,a6 Prot:8]
[76291.212067] src IP:'192.168.122.1', dst IP:'192.168.122.161'
[76291.213067] [MAC_DES:52,54,0,df,93,bf MAC_SRC: 52,54,0,11,e4,a6 Prot:8]
[76291.213068] src IP:'192.168.122.1', dst IP:'192.168.122.161
小结:主要目的是使用nf_register_net_hook
函数,利用Netfiler框架做一下小实验,显示包的一些信息,后面会继续说一下,Netfilter是如何调用hook函数的。
参考链接
https://github.com/Douane/douane-dkms/issues/26
http://wiki.dreamrunner.org/public_html/Linux/Networks/netfilter.html