Linux netfilter 源码分析
内容基本上来自两篇文章:
《Netfilter 源码分析》—(独孤九贱 http://www.skynet.org.cn/index.php)
《Linux Netfilter 实现机制和扩展技术》——(杨沙洲 国防科技大学计算机学院)
一、 IP 报文的接收到 hook 函数的调用
1.1
ip_input.c
ip_rcv()函数
以接收到的报文为例,类似的还有 ip_forward(ip_forward.c)和 ip_output(ip_output.c)
int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt,
struct net_device *orig_dev)
{
struct iphdr *iph;
//定义一个 ip 报文的数据报头
u32 len;
if (skb->pkt_type == PACKET_OTHERHOST)
goto drop;
//数据包不是发给我们的
IP_INC_STATS_BH(IPSTATS_MIB_INRECEIVES); //收到数据包统计量加 1
if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL)
{
/* 如果数据报是共享的,则复制一个出来,此时复制而出的已经和 socket 脱离了关系 */
IP_INC_STATS_BH(IPSTATS_MIB_INDISCARDS);
goto out;
}
if (!pskb_may_pull(skb, sizeof(struct iphdr)))
goto inhdr_error;
//对数据报的头长度进行检查,
iph = skb->nh.iph;
//取得数据报的头部位置
if (iph->ihl < 5 || iph->version != 4)
//版本号或者头长度不对,
goto inhdr_error; //头长度是以 4 字节为单位的,所以 5 表示的是 20 字节
if (!pskb_may_pull(skb, iph->ihl*4))
goto inhdr_error;
if (unlikely(ip_fast_csum((u8 *)iph, iph->ihl)))
goto inhdr_error; //检查报文的检验和字段
len = ntohs(iph->tot_len);
if (skb->len < len || len < (iph->ihl*4))
goto inhdr_error; //整个报文长度不可能比报头长度小
if (pskb_trim_rcsum(skb, len))
{ //对数据报进行裁减,这样可以分片发送过来的数据报不会有重复数据
IP_INC_STATS_BH(IPSTATS_MIB_INDISCARDS);
goto drop;
}
return NF_HOOK(PF_INET, NF_IP_PRE_ROUTING, skb, dev, NULL,
ip_rcv_finish); //通过回调函数调用 ip_rcv_finish
inhdr_error:
IP_INC_STATS_BH(IPSTATS_MIB_INHDRERRORS);
drop:
out:
}
kfree_skb(skb); //丢掉数据报
return NET_RX_DROP;
1.2
include/linux/netfilter.h
NF_HOOK 宏
#ifdef CONFIG_NETFILTER_DEBUG
#define NF_HOOK(pf, hook, skb, indev, outdev, okfn)
\
nf_hook_slow((pf), (hook), (skb), (indev), (outdev), (okfn), INT_MIN)
#define NF_HOOK_THRESH nf_hook_slow
#else
#define NF_HOOK(pf, hook, skb, indev, outdev, okfn)
\
(list_empty(&nf_hooks[(pf)][(hook)])
\
? (okfn)(skb)
\
: nf_hook_slow((pf), (hook), (skb), (indev), (outdev), (okfn), INT_MIN))
#define NF_HOOK_THRESH(pf, hook, skb, indev, outdev, okfn, thresh)
\
(list_empty(&nf_hooks[(pf)][(hook)])
\
? (okfn)(skb)
\
: nf_hook_slow((pf), (hook), (skb), (indev), (outdev), (okfn), (thresh)))
#endif
/*
如果 nf_hooks[PF_INET][NF_IP_FORWARD]所指向的链表为空(即该钩子上没有挂处
理函数),则直接调用 okfn;否则,则调用 net/core/netfilter.c::nf_hook_slow()转入
Netfilter 的处理。 */
1.3
net/core/netfilter.c
nf_kook_slow()函数
int nf_hook_slow(int pf, unsigned int hook, struct sk_buff **pskb,
struct net_device *indev,
struct net_device *outdev,
int (*okfn)(struct sk_buff *),
{
int hook_thresh)
struct list_head *elem;
unsigned int verdict;
int ret = 0;
rcu_read_lock();
/*取得对应的链表首部*/
elem = &nf_hooks[pf][hook];
next_hook:
/*调用对应的钩子函数*/
verdict = nf_iterate(&nf_hooks[pf][hook], pskb, hook, indev,
outdev, &elem, okfn, hook_thresh);
/*判断返回值,做相应的处理*/
if (verdict == NF_ACCEPT || verdict == NF_STOP) {
ret = 1;
/*前面提到过,返回 1,则表示装继续调用 okfn 函数指针*/
goto unlock;
} else if (verdict == NF_DROP) {
kfree_skb(*pskb);
/*删除数据包,需要释放
skb*/
ret = -EPERM;
} else if (verdict == NF_QUEUE) {
NFDEBUG("nf_hook: Verdict = QUEUE.\n");
if (!nf_queue(*pskb, elem, pf, hook, indev, outdev, okfn))
goto next_hook;
}
unlock:
}
rcu_read_unlock();
return ret;
1.4
net/core/netfilter.c
nf_iterate()函数
static unsigned int nf_iterate(struct list_head *head,
struct sk_buff **skb,
int hook,
const struct net_device *indev,
const struct net_device *outdev,
struct list_head **i,
int (*okfn)(struct sk_buff *),
int hook_thresh)
{
/*
* The caller must not block between calls to this
* function because of risk of continuing from deleted element.
*/
/* 依次调用指定 hook 点下的所有 nf_hook_ops->(*hook)函数,这些 nf_hook_ops 里有
filter 表注册的,有 mangle 表注册的,等等。
list_for_each_continue_rcu 函数是一个 for 循环的宏,当调用结点中的 hook 函数后,根
据返回值进行相应处理。如果 hook 函数的返回值是 NF_QUEUE,NF_STOLEN,NF_DROP 时,函数
返回该值;如果返回值是 NF_REPEAT 时,则跳到前一个结点继续处理;如果是其他值,由下
一个结点继续处理。如果整条链表处理完毕,返回值不是上面四个值,则返回 NF_ACCEPT。
*/
list_for_each_continue_rcu(*i, head) {
struct nf_hook_ops *elem = (struct nf_hook_ops *)*i;
if (hook_thresh > elem->priority)
continue;
switch (elem->hook(hook, skb, indev, outdev, okfn)) {
case NF_QUEUE:
return NF_QUEUE;
case NF_STOLEN:
return NF_STOLEN;
case NF_DROP:
return NF_DROP;
case NF_REPEAT:
*i = (*i)->prev;
break;
}
}
return NF_ACCEPT;
}
二、ipt_table 数据结构和表的初始化
2.1
include/linux/netfilter_ipv4/ip_tables.h
struct
ipt_table 表结构
struct ipt_table
{
struct list_head list;
/* 表链 */
char name[IPT_TABLE_MAXNAMELEN];
/* 表名,如"filter"、"nat"等,为了满足自动模块加载的设计,包含该表的模块应命名为
iptable_'name'.o */
struct ipt_replace *table;
/* 表模子,初始为 initial_table.repl */
unsigned int valid_hooks;
/* 位向量,标示本表所影响的 HOOK */
rwlock_t lock;
/* 读写锁,初始为打开状态 */
struct ipt_table_info *private;
/* iptable 的数据区,见下 */
struct module *me;
/* 是否在模块中定义 */
};
2.2
struct ipt_table_info 是实际描述表的数据结构 ip_tables.c
struct ipt_table_info
{
unsigned int size;
/* 表大小 */
unsigned int number;
/* 表中的规则数 */
unsigned int initial_entries;
/* 初始的规则数,用于模块计数 */
unsigned int hook_entry[NF_IP_NUMHOOKS];
/* 记录所影响的 HOOK 的规则入口相对于下面的 entries 变量的偏移量 */
unsigned int underflow[NF_IP_NUMHOOKS];
/* 与 hook_entry 相对应的规则表上限偏移量,当无规则录入时,相应的 hook_entry 和
underflow 均为 0 */
char entries[0] ____cacheline_aligned;
/* 规则表入口 */
};
2.3
include/linux/netfilter_ipv4 规则用 struct ipt_entry 结构表示,包含匹配用
的 IP 头部分、一个 Target 和 0 个或多个 Match。由于 Match 数不定,所以一条规则实际的
占用空间是可变的。结构定义如下
struct ipt_entry
{
struct ipt_ip ip;
/* 所要匹配的报文的 IP 头信息 */
unsigned int nfcache;
/* 位向量,标示本规则关心报文的什么部分,暂未使用 */
u_int16_t target_offset;
/* target 区的偏移,通常 target 区位于 match 区之后,而 match 区则在 ipt_entry 的末
尾;
初始化为 sizeof(struct ipt_entry),即假定没有 match */
u_int16_t next_offset;
/* 下一条规则相对于本规则的偏移,也即本规则所用空间的总和,
初始化为 sizeof(struct ipt_entry)+sizeof(struct ipt_target),即没有 match */
unsigned int comefrom;
/* 规则返回点,标记调用本规则的 HOOK 号,可用于检查规则的有效性 */
struct ipt_counters counters;
/* 记录该规则处理过的报文数和报文总字节数 */
unsigned char elems[0];
/*target 或者是 match 的起始位置 */
}
2.4 iptables 的初始化 init(void) ,以 filter 表为例 iptable_filter.c
static int __init init(void)
{
int ret;
if (forward < 0 || forward > NF_MAX_VERDICT) {
printk("iptables forward must be 0 or 1\n");
return -EINVAL;
}
/* Entry 1 is the FORWARD hook */
initial_table.entries[1].target.verdict = -forward - 1;
/* Register table */
ret = ipt_register_table(&packet_filter);
//注册 filter 表
if (ret < 0)
return ret;
/* Register hooks */
ret = nf_register_hook(&ipt_ops[0]);
//注册三个 HOOK
if (ret < 0)
goto cleanup_table;
ret = nf_register_hook(&ipt_ops[1]);
if (ret < 0)
goto cleanup_hook0;
ret = nf_register_hook(&ipt_ops[2]);
if (ret < 0)
goto cleanup_hook1;
return ret;
cleanup_hook1:
nf_unregister_hook(&ipt_ops[1]);
cleanup_hook0:
nf_unregister_hook(&ipt_ops[0]);
cleanup_table:
ipt_unregister_table(&packet_filter);
return ret;
}
/* ipt_register_table 函数的参数 packet_filter 包含了待注册表的各个参数 */
static struct ipt_table packet_filter = {
.name
.table
= "filter",
= &initial_table.repl,
.valid_hooks
= FILTER_VALID_HOOKS,
.lock
= RW_LOCK_UNLOCKED,
.me
= THIS_MODULE
};
/* 上面的&initial_table.repl 是一个 ipt_replace 结构,也就是 ipt_table-〉*table 的
初始值。
下面是 ipt_replace 结构的定义,它和 ipt_table_info 很相似,基本上就是用来初始化
ipt_table 中的 ipt_table_info *private 的,这个结构不同于 ipt_table_info 之处在于,
它还要保存表的旧的规则信息 */
struct ipt_replace
{
};
char name[IPT_TABLE_MAXNAMELEN];
/* 表名 */
unsigned int valid_hooks;
unsigned int num_entries;
unsigned int size;
/* 影响的 hook */
/* entry 数 */
/* entry 的总大小 */
unsigned int hook_entry[NF_IP_NUMHOOKS];
/* 规则入口的偏移值 */
unsigned int underflow[NF_IP_NUMHOOKS];
/* 规则的最大偏移值 */
unsigned int num_counters;
/* 规则数 */
struct ipt_counters __user *counters;
struct ipt_entry entries[0];
/* 规则入口 */
/* 下面是 initial_table.repl 的初始化 */
static struct
{
struct ipt_replace repl;
struct ipt_standard entries[3];
struct ipt_error term;
} initial_table __initdata
= { { "filter", FILTER_VALID_HOOKS, 4,
sizeof(struct ipt_standard) * 3 + sizeof(struct ipt_error),
{ [NF_IP_LOCAL_IN] = 0,
[NF_IP_FORWARD] = sizeof(struct ipt_standard),
[NF_IP_LOCAL_OUT] = sizeof(struct ipt_standard) * 2 },
{ [NF_IP_LOCAL_IN] = 0,
[NF_IP_FORWARD] = sizeof(struct ipt_standard),
[NF_IP_LOCAL_OUT] = sizeof(struct ipt_standard) * 2 },
0, NULL, { } },
{
/* LOCAL_IN */
{ { { { 0 }, { 0 }, { 0 }, { 0 }, "", "", { 0 }, { 0 }, 0, 0, 0 },
0,
sizeof(struct ipt_entry),
sizeof(struct ipt_standard),