通俗的说,netfilter的架构就是在整个网络流程的若干位置放置了一些检测点(HOOK),而在每个检测点上登记了一些处理函数进行处理(如包过滤,NAT等,甚至可以是用户自定义的功能)
1. IP层的五个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包过滤在此点进行
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
};
2. 通讯方式
3. 支持的协议类型
enum {
NFPROTO_UNSPEC = 0,
NFPROTO_IPV4 = 2,
NFPROTO_ARP = 3,
NFPROTO_BRIDGE = 7,
NFPROTO_IPV6 = 10,
NFPROTO_DECNET = 12,
NFPROTO_NUMPROTO,
};
4. 钩子函数
typedef unsigned int nf_hookfn(unsigned int hooknum,
struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
int (*okfn) (struct sk_buff *));
in:进来的设备
out:出去的设备
okfn:是个函数指针,当所有的该HOOK点的所有登记函数调用完后,转而走此流程
/* 处理函数返回值 */
#define NF_DROP 0 /* drop the packet, don't continue traversal */
#define NF_ACCEPT 1 /* continue traversal as normal */
#define NF_STOLEN 2 /* I've taken over the packet, don't continue traversal */
#define NF_QUEUE 3 /* queue the packet (usually for userspace handling) */
#define NF_REPEAT 4 /* call this hook again */
#define NF_STOP 5
#define NF_MAX_VERDICT NF_STOP
NF_ACCEPT:继续正常传输数据报。这个返回值告诉 Netfilter:到目前为止,该数据包还是被接受的并且该数据包应当被递交到网络协议栈的下一个阶段
NF_DROP:丢弃该数据报,不再传输
NF_STOLEN:模块接管该数据报,告诉Netfilter“忘掉”该数据报。该回调函数将从此开始对数据包的处理,并且Netfilter应当放弃对该数据包做任何的处理。但是,这并不意味着该数据包的资源已经被释放。这个数据包以及它独自的sk_buff数据结构仍然有效,只是回调函数从Netfilter 获取了该数据包的所有权
NF_QUEUE:对该数据报进行排队
NF_REPEAT:再次调用该回调函数,应当谨慎使用这个值,以免造成死循环
5. Netfilter实体
在使用Netfilter时,需要定义一个nf_hook_ops实例
struct nf_hook_ops {
struct list_head list;
/* User fills in from here down. */
nf_hookfn *hook; /* 要注册的钩子函数 */
struct module *owner;
u_int8_t pf; /* 协议类型 */
unsigned int hooknum; /* 哪个HOOK */
/* Hooks are ordered in asending priority. */
int priority; /* 数值越小,优先级越高 */
};
typedef __u8 u_int8_t;
pf:协议族名,netfilter架构同样可以用于IP层之外,因此这个变量还可以有诸如PF_INET6,PF_DECnet等名字
6. 注册与注销
int nf_register_hook(struct nf_hook_ops *reg);
void nf_unregister_hook(struct nf_hook_ops *reg);
如果我们想加入自己的代码,便要用nf_register_hook函数。我们的工作便是生成一个struct nf_hook_ops结构的实例,并用nf_register_hook将其HOOK上
一个HOOK点可能挂多个处理函数,谁先谁后,便要看优先级,即priority的指定了
7. 举例
通过在PRE_ROUTING处注册my_hookfn,改变接收数据包的源IP为8.8.8.8
#include <linux/netfilter.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/netfilter_ipv4.h>
#include <linux/ip.h>
#include <linux/inet.h>
/**
* Hook function to be called.
* We modify the packet's src IP.
*/
unsigned int my_hookfn(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;
iph = ip_hdr(skb);
/* log the original src IP */
printk(KERN_INFO"src IP %pI4\n", &iph->saddr);
/* modify the packet's src IP */
iph->saddr = in_aton("8.8.8.8");
return NF_ACCEPT;
}
/* A netfilter instance to use */
static struct nf_hook_ops nfho = {
.hook = my_hookfn,
.pf = PF_INET,
.hooknum = NF_INET_PRE_ROUTING,
.priority = NF_IP_PRI_FIRST,
.owner = THIS_MODULE,
};
static int __init sknf_init(void)
{
if (nf_register_hook(&nfho)) {
printk(KERN_ERR"nf_register_hook() failed\n");
return -1;
}
return 0;
}
static void __exit sknf_exit(void)
{
nf_unregister_hook(&nfho);
}
module_init(sknf_init);
module_exit(sknf_exit);
MODULE_AUTHOR("zhangsk");
MODULE_LICENSE("GPL");
8. NF_QUEUE
skb交由用户空间的hook来处理,处理完毕后,使用nf_reinject返回内核空间继续执行
前提是驱动代码注册了nf_register_queue_handle队列处理回调
pf:协议类型
qh:NF中对报文进行Queue的结构体
/* Packet queuing */
struct nf_queue_handler {
int (*outfn)(struct sk_buff *skb, struct nf_info *info,
unsigned int queuenum, void *data);
void *data;
char *name;
};
static struct nf_queue_handler nfqh = {
.name = "ip_queue",
.outfn = &ipq_enqueue_packet,
};
nf_reinject(struct ipq_queue_entry *entry, NF_ACCEPT);
struct ipq_queue_entry {
struct list_head list;
struct nf_info *info;
struct sk_buff *skb;
};
list:是个双向链表结构,用于将所有queue的数据包用双向链表连
接起来,实现队列管理
skb:数据包
9. 举例
static int __init xt_xxx_target_init(void)
{
int status = 0;
status = nf_register_queue_handler(NFPROTO_IPV4, &nfqh);
if (status < 0) {
printk("XXX: register queue handler error\n");
goto err;
}
err:
return status;
}
static void __exit xt_xxx_target_exit(void)
{
nf_unregister_queue_handlers(&nfqh);
}
module_init(xt_xxx_target_init);
module_exit(xt_xxx_target_exit);