1. Netfilter简介
Netfilter是由Rusty Russell提出的自Linux 2.4添加的内核防火墙框架,该框架既简洁又灵活,可实现安全策略应用中的许多功能,如数据包过滤、数据包处理、地址伪装、透明代理、动态网络地址转换(Network Address Translation,NAT),以及基于用户及媒体访问控制(Media Access Control,MAC)地址的过滤和基于状态的过滤、包速率限制等。
Netfilter的架构就是在数据包在通过网络协议栈的若干位置放置了一些检测点(HOOKS),不同防火墙功能的子模块通过netfilter框架提供的API注册相应的HOOK回调函数,并在回调函数中实现对数据包的过滤、解包修改后重新打包等操作。已注册的HOOK回调函数会在数据包流经netfilter框架HOOK点时被系统自动调用执行。
2.Hook机制及工作流程
Netfilter的钩子函数(HOOK)有以下五个挂载点,在<Linux/netfilter_ipv4.h>中被定义:
NF_IP_PRE_ROUTING (0): 在被路由代码处理之前, 传入包在 ip_rcv() 函数中经过这个钩子。在那之前,只有一些简单的关于版本、长度和IP报头中的校验和字段等一致性检查要做。
NF_IP_LOCAL_IN (1): 所有地址指向本地计算机的传入包都在 ip_local_deliver() 函数中通过该钩子。在此,iptables 模块钩住 INPUT 规则列表来筛选传入的数据包。 这对应于 ipchains 里面的输入规则列表。
NF_IP_FORWARD (2): 所有地址不指向本地计算机的传入包都在 ip_forward() 函数
中经过这个钩子——也就是说,需要转发的包和在不同网络接口上被发出的包都要经过。
这包括所有被 NAT 修改过地址的包。在此,iptables 模块钩住 FORWARD 规则列表来筛选数据包。 这对应于 ipchains 里面的转发规则列表。
NF_IP_LOCAL_OUT (3): 所有在本地计算机上创建的发包在raw_send_hdrinc() 函数中通过这个钩子钩住 OUTPUT 规则列表来筛选外发的数据包。
NF_IP_POST_ROUTING (4): ip_mc_output() 函数中的这个钩子是在所有外发包通过网络设备离开本地计算机之前访问它们的最后机会。与NF_IP_PRE_ROUTING钩子一样,这也是集成计数功能的良好位置。
以上五个点的数据流向图如图所示:
-
当网络上有数据包到来时,由驱动程序将数据包从网卡内存区通过DMA转移到设备主存区(内存区), 之后触发中断通知CPU进行异步响应,之后ip_rcv函数会被调用到;
-
ip_rcv函数首先对报文进行检验,最后调用NF_HOOK函数将控制权交给在NF_IP_PRE_ROUTING注册的规则进行处理,之后数据到达ip_rcv_finish函数并进行路由标的查询,确定该报文是发往本地还是转发。
-
如果该报文是发往本地的,则调用ip_local_deliver函数进行处理,之后将控制权交给在NF_IP_LOCAL_IN注册的规则进行处理, 处理完毕后由ip_local_deliver_finish将数据报文发送到传输层;
-
如果该报文是转发报文,则先后经过NF_IP_FORWARD和NF_IP_POST_ROUTING注册的规则,最后交由驱动程序发送到网络中;
-
-
对于本地外发报文会先后经过NF_IP_LOCAL_OUT和NF_IP_POST_ROUTING注册的规则,最后同样交由驱动程序发送到网络中。
-
3.钩子函数的定义
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;
/* Hooks are ordered in ascending priority. */
int priority;
};
其中,nf_hookfn *hook是要进行注册的钩子函数,pf是协议标识符,定义在<linux/netfilter.h>中:
enum {
NFPROTO_UNSPEC = 0,
NFPROTO_IPV4 = 2,
NFPROTO_ARP = 3,
NFPROTO_BRIDGE = 7,
NFPROTO_IPV6 = 10,
NFPROTO_DECNET = 12,
NFPROTO_NUMPROTO,
};
hooknum控制挂载点,其定义为:
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为优先级,priority越小的,优先级越高;在同一挂载点下,所有钩子函数将按照优先级顺序依次执行:
enum nf_ip_hook_priorities {
NF_IP_PRI_FIRST = INT_MIN,
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_CONFIRM = INT_MAX,
NF_IP_PRI_LAST = INT_MAX,
};
钩子函数将根据函数返回值决定如何处理该包,返回值应当为无符号整型:
NF_DROP(0):活动规则列表的处理被停止,并且将包丢弃。
NF_ACCEPT(1):将包传递给规则列表中的下一个包筛选函数。当到达了列表的末端, okfn()函数释放该包以供进一步处理。
NF_STOLEN(2):包筛选函数拒绝将包释放给进一步的处理步骤,以致活动规则列表的处理到此为止。与NF_DROP不同之处在于不必丢弃包,而是由包筛选函数进行后续处理。
NF_QUEUE(3): nf_queue()函数 (net/core/netfilter.c) 将包放入队列中, 该队列中的包可以被 (用户空间程序等) 删除和处理。 然后, 必须调用 nf_reinject()来将包返回给 Linux 内核以供 netfilter 进一步处理。
NF_REPEAT(4):与NF_ACCEPT的继续调用下一个包筛选函数不同,该返回值将再次调用当前筛选函数。
NF_STOP(5):继续正常传输报文。NF_ACCEPT和NF_STOP都表示报文通过了检查,可以正常向下流通,但是NF_STOP效果强于NF_ACCEPT。在每个处理点可以注册多个钩子函数,这些钩子函数按照优先级排列。优先级高的钩子函数先处理报文,优先级低的报文后处理报文。NF_ACCEPT表示报文通过了某个钩子函数的处理,下一个钩子函数可以接着处理了;而NF_STOP表示报文通过了某个钩子函数的处理,后面的钩子函数不再执行,而是直接流向下一环节。
4.钩子函数的注册和使用
钩子函数使用的接口为
nf_register_hooks(nf_packet_filter_ops, ARRAY_SIZE(nf_packet_filter_ops))
使用该函数即可将定义的钩子函数注册到内核,nf_unregister_hooks为对应的卸载函数。
值得注意的是,nf_register_hooks() 与 nf_unregister_hooks() 为使用EXPORT_SYMBOL导出的内核符号。