随着计算机网络和Internet普及,计算机很久之前就开始遭受各种入侵了。因此为了阻止入侵,就产生了网络防火墙以及网络数据分析的需求。
而这个netfilter就是在linux系统中来实现防火墙功能。
netfilter是Linux 2.4.x引入的一个Linux内核框架,提供一整套的hook函数的管理机制。可以根据动态定义的条件来过滤和操作分组。从防火墙到网络通信数据的详细分析,到分组过滤器,都可以实现。
其在内核中的位置如下图:
图中也反映了我们常用的用户层命令iptables和底层netfilter的关系。
iptables是Linux的应用层防火墙工具,使用时候主要关注的是chain和table,其关联如下。
5个不同的chain其实就是5个过滤点,3个表根据功能和表的定义划分。因为其是在应用层,所以我们关注的是他的编写。
l  [-t 表名]:该规则所操作的哪个表,可以使用filter、nat等,如果没有指定则默认为filter
l  -A:新增一条规则,到该规则链列表的最后一行
l  -I:插入一条规则,原本该位置上的规则会往后顺序移动,没有指定编号则为1
l  -D:从规则链中删除一条规则,要么输入完整的规则,或者指定规则编号加以删除
l  -R:替换某条规则,规则替换不会改变顺序,而且必须指定编号。
l  -P:设置某条规则链的默认动作
l  -nL:-L、-n,查看当前运行的防火墙规则列表
l  chain名:指定规则表的哪个链,如INPUT、OUPUT、FORWARD、PREROUTING等
l  [规则编号]:插入、删除、替换规则时用,--line-numbers显示号码
l  [-i|o 网卡名称]:i是指定数据包从哪块网卡进入,o是指定数据包从哪块网卡输出
l  [-p 协议类型]:可以指定规则应用的协议,包含tcp、udp和icmp等
l  [-s 源IP地址]:源主机的IP地址或子网地址
l  [--sport 源端口号]:数据包的IP的源端口号
l  [-d目标IP地址]:目标主机的IP地址或子网地址
l  [--dport目标端口号]:数据包的IP的目标端口号
l  -m:extend matches,这个选项用于提供更多的匹配参数,如:
n  -m state --state ESTABLISHED,RELATED
n  -m tcp --dport 22
n  -m multiport --dports 80,8080
n  -m icmp --icmp-type 8
n  <-j 动作>:处理数据包的动作,包括ACCEPT、DROP、REJECT等。
例如禁止对外向某个IP发送数据包。
#iptables -t filter -A INPUT -s 192.168.1.10 -j DROP
-表:规定使用的表(filter、nat、mangle,不同的表有不同的功能)
-链:规定过滤点
-匹配属性:规定匹配数据包的特征
-匹配后的动作
使用iptables -L查看规则。
删除现有规则:iptable -F
netfilter钩子能在6挂接点注册回调函数,也是挂钩编号:
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
};
定义在include/uapi/linux/netfilter.h
netfilter钩子目的是在支持运行阶段加载netfilter内核模块。
我们知道netfilter的入口是在ip层的,所以其数据包入口其实就是ip包,不刚好是用户层的命令iptables相对应么?是不是感觉世界也变得清晰起来了。
那么这些挂钩函数是如何调用的呢?
以上图中NF_INET_LOCAL_IN这个关键点为例,我们来看下其上游函数ip_local_deliver()函数,如下:
/*
* Deliver IP Packets to the higher protocol layers.
*/
int ip_local_deliver(struct sk_buff *skb)
{
/*
* Reassemble IP fragments.
*/
struct net *net = dev_net(skb->dev);
if (ip_is_fragment(ip_hdr(skb))) {
if (ip_defrag(net, skb, IP_DEFRAG_LOCAL_DELIVER))
return 0;
}
return NF_HOOK(NFPROTO_IPV4, NF_INET_LOCAL_IN,
net, NULL, skb, skb->dev, NULL,
ip_local_deliver_finish);
}
可以看到挂钩函数通过NF_HOOK宏调用(其它过滤点同理),该宏是定义在include/linux/netfilter.h文件中的。是netfilter钩子,如果允许包传递则返回1。如果返回其他值说明这个包被hook给消耗掉了。
static inline int nf_hook(u_int8_t pf, unsigned int hook, struct net *net,
struct sock *sk, struct sk_buff *skb,
struct net_device *indev, struct net_device *outdev,
int (*okfn)(struct net *, struct sock *, struct sk_buff *))
其中pf表示挂钩的协议族,
hook是挂钩编号,
skb是处理的套接字缓冲区
indev和outdev是网络设备的net_device实例的指针。
okfn是函数指针,在netfilter挂钩结束时候执行。
每个钩子函数最后必须向Netfilter框架返回下列几个值其中之一,定义在include/uapi/linux/netfilter.h:
NF_DROP 丢弃该数据报,不再传输。
NF_ACCEPT 继续正常传输数据报。这个返回值告诉 Netfilter:到目前为止,该数据包还是被接受的并且该数据包应当被递交到网络协议栈的下一个阶段。
NF_STOLEN 模块接管该数据报,告诉Netfilter“忘掉”该数据报。该回调函数将从此开始对数据包的处理,并且Netfilter应当放弃对该数据包做任何的处理。但是,这并不意味着该数据包的资源已经被释放。这个数据包以及它独自的sk_buff数据结构仍然有效,只是回调函数从Netfilter 获取了该数据包的所有权。
NF_QUEUE 对该数据报进行排队(通常用于将数据报给用户空间的进程进行处理)
NF_REPEAT 再次调用该回调函数,应当谨慎使用这个值,以免造成死循环。
NF_STOP为了兼容用户层的NF_QUEUE。
在之前的文章中有提到抓包工具tcpdump使用的是AF_PACKET套接字,而不是Netfilter。因为有一下几点:
l  数据包进入netfilter时已经做过一些处理,数据包可能发生了变化。另外,进入netfilter前需要重组分片,无法抓取到原始的报文分片。
l  另外如果是发送,报文离开netfilter时也未完全结束协议栈的处理,导致报文不完整。
l  在Netfilter抓取的报文,实现较为复杂。
所以用AF_PACKET来进行报文抓取。
小结一下,总体逻辑过程其实非常简单。
在linux网络内核协议栈中,已预定义的关键点PRE_ROUTING、LOCAL_IN、FORWARD、LOCAL_OUT和POST_ROUTING,当数据包经过这些关键点时候,会根据数据包的协议簇PF_INET去查找是否注册有钩子函数。
如果没有,则继续走协议栈;如果有则进入到netfilter框架中去进一步调用已注册在该点下的钩子函数,最后再根据其返回值来确定是否继续执行,因为数据包可能被干掉了。
本片主要是一些前提的知识点和逻辑,最后祝大家玩得开心。
洞悉linux下的Netfilter&iptables:什么是Netfilter?
《Linux Kernel Networking》