-
Linux netfilter
源码分析
内容基本上来自两篇文章
:
《
Netfilter
源码分析》—(独孤九贱
/
)
《
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
< br>报文的数据报头
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->; //
取得数据报的头部位置
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:
p>
IP_INC_STATS_BH(IPSTATS_MIB_INHDRERRORS);
drop:
kfree_skb(skb);
//
丢掉数据报
out:
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)
#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)
:
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)
#endif
/*
如果
nf_hooks[PF_INET][NF_IP_FORWA
RD]
所指向的链表为空(即该钩子上没有挂处
理函数),则直
接调用
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 *),
: nf_hook_slow((pf), (hook), (skb),
(indev), (outdev), (okfn), (thresh)))
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(
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_hoo
k_ops
里有
filter
表注册的
,有
mangle
表注册的,等等。
list_for_each_continue_rcu
函数
是一个
for
循环的宏,当调用结点中的
hook
函数后,根
据返回值进行相应处理。
如果
hook
函数的返回值是
NF_QUEUE,NF_STOLEN,NF_DROP
时,
函数
返回该值;
如果返回值是
NF_R
EPEAT
时,则跳到前一个结点继续处理;如果是其他值,由下
一个结点继续处理。如果整条链表处理完毕,返回值不是上面四个值,则返回
NF_A
CCEPT
。
*/
}
二、
ipt_table
数据结构和表的初始化
}
return NF_ACCEPT;
case
NF_REPEAT:
}
*i = (*i)->prev;
break;
case NF_DROP:
return NF_DROP;
case NF_STOLEN:
return NF_STOLEN;
switch
(elem->hook(hook, skb, indev, outdev, okfn)) {
case NF_QUEUE:
return NF_QUEUE;
if (hook_thresh >
elem->priority)
continue;
list_for_each_continue_rcu(*i, head) {
struct nf_hook_ops *elem =
(struct nf_hook_ops *)*i;
2.1
include/linux/netfilter_ipv4/ip_tables.h struct
ipt_table
表结构
struct ipt_table
{
struct list_head list;
/*
表链
*/
char
name[IPT_TABLE_MAXNAMELEN];
/*
< br>表名,
如
、
等,为了满足自动模块加载的设计,包含该表的模块应命名为
iptable_'name'.o */
struct ipt_replace
*table;
/*
表模子,初始为
initial_ */
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];
/*
< br>记录所影响的
HOOK
的规则入口相对于下面的
entries
变量的偏移量
*/
unsigned int underflow[NF_IP_NUMHOOKS];
/*
与
hook_entry
相对应的规则表上限偏移量,当无规则录入时,相应的
hook_en
try
和
underflow
均为
p>
0 */
char entries[0]
____cacheline_aligned;
/*
规则表入口
*/
};
2.3
include/linux/netfilter_ipv4
规则用
struct
ipt_ent
ry
结构表示,包含匹配用
的
IP
p>
头部分、
一个
Target
和
0
个或多个
Match
p>
。
由于
Match
数不定,
所以一条规则实际的
占用空间是可变的。结构定义如下
struct
ipt_entry
{
struct ipt_ip
ip;
/*
所要匹配的报文的
IP
头信息
*/
unsigned
int nfcache;
/*
位向量,标示本规则关心报文的什么部分,暂未使用
*/
u_int16_t target_offset;
/*
target
区的偏移,通常
target
区位于
match
区之后,而
ma
tch
区则在
ipt_entry
的末
尾;
初始化为
sizeof(struct
ipt_entry)
,即假定没有
match */
u_int16_t next_offset;
/*
下一条规则相对于本规则的偏移,也即本规则所用空间的总和,
初始化为
sizeof(struct
ipt_entry)+sizeof(struct
ipt_target)
,即没有
match */
unsigned int comefrom;
/*
p>
规则返回点,标记调用本规则的
HOOK
号
,可用于检查规则的有效性
*/
struct
ipt_counters counters;
/*
记录该规则处理过的报文数和报文总字节数
*/
unsigned char elems[0];
/*ta
rget
或者是
match
的起始位置
*/
}
2.4
iptables
的初始化
init(void)
,以
filter
表为例
iptable_filter.c
static int __init
init(void)
{
/* Entry 1 is
the FORWARD hook */
if (forward < 0 ||
forward > NF_MAX_VERDICT) {
}
printk(
return -EINVAL;
int ret;
initial_s[1].t = -forward - 1;
/* Register table */
ret =
ipt_register_table(&packet_filter); //
< br>注册
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:
}
/*
ipt_register_table
函数的参数
packet_filter
包含了待注册表的各个参数
< br>
*/
static
struct ipt_table packet_filter = {
};
.name
.table
.lock
.me
=
= &initial_,
=
FILTER_VALID_HOOKS,
= RW_LOCK_UNLOCKED,
return ret;
nf_unregister_hook(&ipt_ops[1]);
nf_unregister_hook(&ipt_ops[0]);
ipt_unregister_table(&packet_filter);
cleanup_hook0:
cleanup_table:
.valid_hooks
= THIS_MODULE
/*
上面的
&initial_
是一个
p>
ipt_replace
结构,也就是
ip
t_table-
〉
*table
的<
/p>
初始值。
下面是
ipt_replace
结构的定义,它和
ipt_tabl
e_info
很相似,基本上就是用来初始化
ipt_tabl
e
中的
ipt_table_info *private
p>
的,这个结构不同于
ipt_table_info
之处在于,
它还要保存表的旧的规则信息
*/
struct
ipt_replace
{
};
/*
下面是
initial_
的初始化
*/
static struct
{
struct
ipt_replace repl;
struct ipt_standard
entries[3];
struct ipt_error term;
char name[IPT_TABLE_MAXNAMELEN]; /*
表名
*/
unsigned
int valid_hooks; /*
影响的
hook */
unsigned int num_entries;
/* entry
数
*/
unsigned int size;
/* 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 __initdata
= { {
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_FORWARD] =
sizeof(struct ipt_standard),
[NF_IP_LOCAL_OUT] = sizeof(struct
ipt_standard) * 2 },
{
[NF_IP_LOCAL_IN] = 0,
0, NULL, {
} },
{
/* LOCAL_IN */
{ { { { 0 }, { 0 },
{ 0 }, { 0 },
0,
sizeof(struct ipt_entry),
sizeof(struct ipt_standard),
0, { 0, 0 }, { } },
-NF_ACCEPT - 1 } },
{
{ { { IPT_ALIGN(sizeof(struct
ipt_standard_target)),
/* FORWARD
*/
{ { { { 0 }, { 0 }, { 0 }, { 0
},
0,
sizeof(struct
ipt_entry),
sizeof(struct
ipt_standard),
0, { 0, 0 }, { } },
-NF_ACCEPT - 1 } },
{
{ { { IPT_ALIGN(sizeof(struct
ipt_standard_target)),
/* LOCAL_OUT
*/
{ { { { 0 }, { 0 }, { 0 }, { 0
},
0,
sizeof(struct
ipt_entry),
sizeof(struct
ipt_standard),
0, { 0, 0 }, { } },
-NF_ACCEPT - 1 } }
{ {
{ { IPT_ALIGN(sizeof(struct ipt_standard_target)),
},
/* ERROR */
{ { { { 0 }, { 0 }, { 0 }, { 0 },
0,
sizeof(struct ipt_entry),
sizeof(struct ipt_error),
0,
{ 0, 0 }, { } },
{ } },
{ { { {
IPT_ALIGN(sizeof(struct ipt_error_target)),
IPT_ERROR_TARGET } },
}
}
};
三、
ip
t_table
表的注册
init
()函数初始化时调用了
ipt_re
gister_table
函数进行表的注册
3.1 ip_tables.c
表的注册
ipt_register_table
int ipt_register_table(struct ipt_table
*table)
{
/*<
/p>
宏
MOD_INC_USE_COUNT
用于模块计数器累加,主要是为了防止模块异常删除,对
应的宏
MOD_DEC_USE_COUNT
就是累减了
*/
/*
为每个
CPU
分配规则空间
*/
/*
将规则项拷贝到新表项的第一个
cpu
空间里面
*/
< br>
/*translate
_table
函数将
newinfo
表
示的
table
的各个规则进行边界检查,然后对于
newinfo
所指的
ipt_talbe_in
fo
结构中的
hook_entries
和
underflows
赋予正确的值,
最后将表项向其他
cpu
拷贝
*/
ret =
down_interruptible(&ipt_mutex);
if (ret
!= 0) {
vfree(newinfo);
MOD_DEC_USE_COUNT;
return
ret;
ret = translate_table(table->name,
table->valid_hooks,
}
newinfo,
table->table->size,
table->table->num_entries,
table->table->hook_entry,
table->table->underflow);
memcpy(newinfo->entries,
table->table->entries, table->table->size);
newinfo = vmalloc(sizeof(struct
ipt_table_info)
}
+ SMP_ALIGN(table->table->size) *
smp_num_cpus);
if (!newinfo) {
ret = -ENOMEM;
MOD_DEC_USE_COUNT;
return
ret;
MOD_INC_USE_COUNT;
int ret;
struct
ipt_table_info *newinfo;
static struct
ipt_table_info bootstrap
=
{ 0, 0, 0, { 0 }, { 0 }, { } };
if (ret
!= 0) {
vfree(newinfo);
MOD_DEC_USE_COUNT;
return
ret;
}
/*
如果注册的
table
已经存在,释放空间
并且递减模块计数
*/
/*
替换
table
项
. */
unlock:
free_unlock:
}
3.2
ip_tables.c
translate_table()
函数
/*
函数
:translate_table()
*
参数:
vfree(newinfo);
MOD_DEC_USE_COUNT;
goto
unlock;
up(&ipt_mutex);
return ret;
table->lock =
RW_LOCK_UNLOCKED;
list_prepend(&ipt_tables, table);
/*
将表添加进链表
*/
duprintf(
/* save number of initial entries */
table->private->initial_entries =
table->private->number;
table->private->number);
/* Simplifies
replace_table code. */
table->private =
&bootstrap;
if (!replace_table(table,
0, newinfo, &ret))
goto
free_unlock;
/* Don't autoload: we'd
eat our tail... */
if
(list_named_find(&ipt_tables, table->name)) {
}
ret = -EEXIST;
goto
free_unlock;
/*
保存初始规则计数器
*/
*
name:
表名称;
*
valid_hooks
p>
:当前表所影响的
hook
*
newinfo
:包含当前表的所有信息的结构
*
size
:表的大小
*
number
:表中的规则数
*
hook_entries
:
记录所影响的
HOOK
的规则入口相对
于下面的
entries
变量的偏移量
*
underflows
:与
hook_entry
相对应的规则表上
限偏移量
*
作用:
*
translate_table
函数将
newinfo<
/p>
表示的
table
的各个规则进行边界检
查,然后对于
newinfo
所指的
i
pt_talbe_info
结构中的
hook_entrie
s
和
underflows
赋予正确的
值,
最后将表项向其他
cpu
拷贝
p>
*
返回值:
*
int ret==0
表示成功返回
*/
static int
translate_table(const char *name,
unsigned int valid_hooks,
struct ipt_table_info *newinfo,
unsigned int size,
unsigned int number,
const unsigned int *hook_entries,
const unsigned int *underflows)
{
unsigned int i;
int ret;
newinfo->size = size;
newinfo->number = number;
/*
初始化所有
Hooks
为不可能的值
. */
for (i = 0; i < NF_IP_NUMHOOKS; i++) {
newinfo->hook_entry[i] = 0xFFFFFFFF;
newinfo->underflow[i] = 0xFFFFFFFF;
}
duprintf(
i = 0;
/*
遍历所有规则,
检查所有偏量,
检查的工作都是由
IPT_ENTRY_ITERATE
这个
宏来完成,并且它的最后一个参数
i
p>
,返回表的所有规则数
. */
ret =
IPT_ENTRY_ITERATE(newinfo->entries, newinfo->size,
check_entry_size_and_hooks,
newinfo,
newinfo->entries,
newinfo->entries + size,
hook_entries, underflows, &i);
if (ret != 0)
return ret;
/*
实际计算得到的规则数与指定
的不符
*/
if (i !=
number) {
duprintf(
i,
number);
return -EINVAL;
}
/*
因为函数一开始将
HOOK
的偏移地址全部初始成了不可能的
值,而在上一个宏
的遍历中设置了
hook_entries<
/p>
和
underflows
的值,这里对它
们进行检查
*/
for (i = 0; i < NF_IP_NUMHOOKS; i++) {
/*
只检查当前表所影响的
hook
*/
if (!(valid_hooks & (1 <<
i)))
continue;
if (newinfo->hook_entry[i] ==
0xFFFFFFFF) {
duprintf(
i, hook_entries[i]);
return -EINVAL;
}
if (newinfo->underflow[i]
== 0xFFFFFFFF) {
duprintf(
i, underflows[i]);
return -EINVAL;
}
}
/*
确保新的
table
中不存在规则
环
*/
if
(!mark_source_chains(newinfo, valid_hooks))
return -ELOOP;
/*
对
tables
中的规则项进行完整性检查,保证每一个规则项在形式上是合法的
*/
i = 0;
ret =
IPT_ENTRY_ITERATE(newinfo->entries, newinfo->size,
check_entry, name, size, &i);
/*
检查失败,释放空间,返回
*/
if (ret != 0) {
IPT_ENTRY_ITERATE(newinfo->entries,
newinfo->size,
cleanup_entry, &i);
return ret;
}
/*
为每个
CPU
复制一个完整的
tab
le
项
*/
for (i = 1; i < smp_num_cpus; i++) {
memcpy(newinfo->entries +
SMP_ALIGN(newinfo->size)*i,
newinfo->entries,
SMP_ALIGN(newinfo->size));
}
return ret;
}
3.3
IPT_ENTRY_ITERAT
宏
ip_tables.h
用来遍历每一个规则,然后调用其第三个参数(函数指针)进
行处理,前两个参数分别表
示规则的起始位置和规则总大小,后面的参数则视情况而定。
#define
IPT_ENTRY_ITERATE(entries, size, fn, args...)
({
})
/*
translate_table
中出现了三次,分别是
*/
IPT_ENTRY_ITERATE(newinfo->entries,
newinfo->size,
check_entry_size_and_hooks,
newinfo,
newinfo->entries,
newinfo->entries + size,
hook_entries, underflows, &i);
unsigned int __i;
int __ret = 0;
struct ipt_entry *__entry;
for (__i = 0; __i < (size); __i +=
__entry->next_offset) {
__entry =
(void *)(entries) + __i;
__ret = fn(__entry , ## args);
if (__ret != 0)
break;
}
__ret;
IPT_ENTRY_ITERATE(newinfo->entries,
newinfo->size,
check_entry,
name, size, &i);
IPT_ENTRY_ITERATE(newinfo->entries,
newinfo->size,
cleanup_entry, &i);
即是在遍历到每条
entry
时分别调用
check_
entry_size_and_hooks
,
check_e
ntry,
cleanup_entry,
三个函数
check_entry
有大用处,后面解释
3.4
list_named_find
(
)函数
listhelp.h
在注册函数中,调用
list_named_find(&ipt_tables,
table->name)
来检查当前表是否已被注册过了
。可见,第一个参数为链表首部,第二个参数为当前表名。
其原型如下:
#define
list_named_find(head, name)
LIST_FIND(head, __list_cmp_name, void
*, name)
#define
LIST_FIND(head, cmpfn, type, args...)
({
const struct
list_head *__i = (head);
ASSERT_READ_LOCK(head);
do {
__i =
__i->next;
if (__i == (head)) {
__i = NULL;
break;
}
} while
(!cmpfn((const type)__i , ## args));
(type)__i;
})
前面提过,表是一个双向链表,在宏当中,以
while
进行循环,以
__i = __i->next;
< br>进行遍历,然后调用比较函数进行比较,传递过来的比较函数是
__list_c
mp_name
。
比较函数很简单:
static
inline int __list_cmp_name(const void *i, const
char *name)
{
return
strcmp(name, i+sizeof(struct list_head)) == 0;
}
3.5
replace_table
()函数
ip_tables.c
表中以
struct ipt_table_info *pr
ivate;
表示实际数据区。但是在初始化赋值的时候,被
设
为
NULL
,而表的初始变量都以模版的形式,放在
struct ipt_replace *table;
中。
注册函数一开始,就声明了:
struct
ipt_table_info *newinfo;
然后对其分配了空间,将模块中
的初值拷贝了进来。所以
replace_table
要做的工
作,主
要就是把
newinfo
中的值
传递给
table
结构中的
priva
te
成员。
replace_table(struct ipt_table *table,
{
if
(num_counters != table->private->number) {
duprintf(
num_counters, table->private->number);
write_lock_bh(&table->lock);
struct ipt_table_info *oldinfo;
unsigned int num_counters,
struct ipt_table_info *newinfo,
int *error)
/* ipt_re
gister_table
函数中,
replace_tabl
e
函数之前有一句
table->private =
p>
&bootstrap;
将
private
初始化为
bootstrap
,即
p>
{ 0
,
0
,
p>
0
,
{0}
,
p>
{0}
,
{}} */
}
3.6
list_prepend
()函数
listhelp.h
当所有的初始化工作结束,就调用
list_prepend
来构建链表了。
static inline void
list_prepend(struct list_head *head,
void *new)
{
ASSERT_WRITE_LOCK(head);
/*
设置写互斥
*/
return oldinfo;
}
oldinfo =
table->private;
table->private =
newinfo;
newinfo->initial_entries =
oldinfo->initial_entries;
write_unlock_bh(&table->lock);
write_unlock_bh(&table->lock);
*error = -EAGAIN;
return
NULL;
list_add(new, head);
/*
将当前表节点添加进链表
*/
}
list_add
就是一个构建双向链表的过程:
static
__inline__ void list_add(struct list_head *new,
struct list_head *head)
{
__list_add(new, head, head->next);
}
static
__inline__ void __list_add(struct list_head * new,
struct list_head * prev,
struct list_head * next)
{
next->prev = new;
new->next = next;
new->prev = prev;
prev->next = new;
}
四、
nf_hook_ops
钩子的注册
在
filter
表的初始化函数
static
int
__init
init(v
oid)
中除了有一个
nf_register_hook
p>
函数注册一个
tables
外,还由
nf_register_hook
函数注册了
3
个
hook
4.1
nf_hook_ops
数据结构
netfilter.h
struct
nf_hook_ops
{
struct
list_head list;
//
链表成员
/* User fills in from here down. */
nf_hookfn *hook;
//
钩子函数指针
struct module *owner;
int pf;
//
协议簇,对于
ipv4
而言,是
PF_INET
int hooknum;
//hook
类型
/* Hooks are ordered in ascending
priority. */
int priority;
//
优先级
};
list
成员用于维护
Netfilter
hook
的列表。
hook
成员是一个指向
nf_hookfn
类型的函数
的指针,该函数是这个
hook
被调用时执
行的函数。
nf_hookfn
同样在
linux/netfilter.h
中定义。
pf
这个成员用于指定协议族。有效的协议族在
linux/socket.h
中列出,但对于
IPv4
我们
使用协议族
PF_INET
。
hooknum
这个成
员用于指定安装的这个函数对应的具体的
hook
类型
:
NF_IP_PRE_ROUTING
在完整性校验之后,选路确定之前
NF_IP_LOCAL_IN
在选路确定之后,且数据包的目的是本地主机
NF_IP_FORWARD
目的地是其它主机地数据包
NF_IP_LOCAL_OUT
来自本机进程的数据包在其离开本地主机的过程中
NF_IP_POST_ROUTING
在数据包离开本地主机
“
上线
”
之前
再看看它的初始化,仍以
filter
表为例
< br>
static struct nf_hook_ops ipt_ops[]
= { { { NULL, NULL }, ipt_hook,
PF_INET, NF_IP_LOCAL_IN,
NF_IP_PRI_FILTER },
{ { NULL, NULL },
ipt_hook, PF_INET, NF_IP_FORWARD,
NF_IP_PRI_FILTER },
{ { NULL, NULL },
ipt_local_out_hook, PF_INET, NF_IP_LOCAL_OUT,
NF_IP_PRI_FILTER }
};
4.2
int
nf_register_hook
函数
netfilter.c
注册实际上就是在一个
nf_hoo
k_ops
链表中再插入一个
nf_hook_ops
结构
int
nf_register_hook(struct nf_hook_ops *reg)
{
spin_lock_bh(&nf_hook_lock);
list_for_each(i,
&nf_hooks[reg->pf][reg->hooknum]) {
}
list_add_rcu(®->list, i->prev);
spin_unlock_bh(&nf_hook_lock);
if (reg->priority < ((struct
nf_hook_ops *)i)->priority)
break;
struct list_head *i;
}
list_for_each
函数遍历当前待注册的钩子的协
议
pf
及
Hook
类型所对应的链表,其首地址
是
&nf_hooks[re
g->pf][reg->hooknum]
,如果当前待注册钩子的优先级小于匹配的
的节点
的优先级,则找到了待插入的位置,也就是说,按优先级的升序排列。
list_add_rcu
< br>把当前节点插入到查到找的适合的位置,这样,完成后,所有
pf
协议下的
hooknum
类型的钩子,
都被注册到
&nf_hooks[reg->pf][reg->hooknu
m]
为首的链表当中了。
4.3
ipt_hook
钩子函数
iptable_raw.c <
/p>
注册
nf_hook_ops
,也就向内
核注册了一个钩子函数,这些函数有
ipt_hook
,
ipt_local_hook
,
ipt_r
oute_hook
,
ipt_local_out_hook
等。
前面在
nf_iterate()
里调用的钩子函数就是它了
下面是
ipt_hook
函数的定义:
static unsigned int
ipt_hook(unsigned int hook,
/* hook
点
*/
{
/*
参数
&packet_filt
er
是由注册该
nf_hook_ops
的表(
filter
)决定的,也有可能是
< br>&packet_raw */
}
实际上是直接调用
ipt_do_table(ip_tables.c)
p>
函数
接下来就是根据
table
里面的
entry
来处
理数据包了
一个
table
就是一组防火墙规则的集合
而一个
entry
就是一条规则,每个
entry
由一系列的
matches
和一个
target
组成
一旦
数据包匹配了该某个
entry
的所有
matches
,就用
target
来
处理它
Match
又分为两部份,一
部份为一些基本的元素,如来源
/
目的地址,进
/
出网口,协议等,
对应了
s
truct ipt_ip
,我们常常将其称为标准的
matc
h
,另一部份
match
则以插件的形
式存
在,是动态可选择,也允许第三方开发的,常常称为扩展的
match
,如字符串匹配,
p2p
匹
配等。同样,规则的
target
也是
可扩展的。这样,一条规则占用的空间,可以分为:
struct
ipt_ip+n*match+n*target
,
(<
/p>
n
表示了其个数,这里的
match
p>
指的是可扩展的
match
部份)
。
return ipt_do_table(pskb,
hook, in, out,
&packet_filter,
NULL);
struct sk_buff **pskb,
const struct net_device
*in,
const struct
net_device *out,
int
(*okfn)(struct sk_buff *))
/*
默认处理函数
*/
synchronize_net();
return 0;
五、
ipt_do_table()
函数,数据包的过滤
5.1
ipt_entry
相关结构
ip_tables.h
ipt_
entry
结构前面有过了,再看一遍
struct ipt_entry
{
struct ipt_ip ip;
/*
所要匹配的报文的
IP
头信息
*/
unsigned int nfcache;
/*
位向量,标示本规则关心报文的什么部分,暂未使用
*/
u_int16_t target_offset;
/*
target
区的偏移,通常
target
区位于
match
区之后,而
ma
tch
区则在
ipt_entry
的末
尾;
初始化为
sizeof(struct
ipt_entry)
,即假定没有
match */
u_int16_t next_offset;
/*
下一条规则相对于本规则的偏移,也即本规则所用空间的总和,
初始化为
sizeof(struct
ipt_entry)+sizeof(struct
ipt_target)
,即没有
match */
unsigned int comefrom;
/*
p>
位向量,标记调用本规则的
HOOK
号,可
用于检查规则的有效性
*/
struct
ipt_counters counters;
/*
记录该规则处理过的报文数和报文总字节数
*/
unsigned char elems[0];
/*ta
rget
或者是
match
的起始位置
*/
}
ipt_ip
结构
ip_tables.h
struct ipt_ip {
};
u_int16_t proto;
/*
协议
, 0 = ANY */
u_int8_t flags;
/*
标志字段
*/
u_int8_t invflags;
/*
取反标志
*/
char iniface[IFNAMSIZ],
outiface[IFNAMSIZ];
/*
输入输出网络接口
*/
unsigned char
iniface_mask[IFNAMSIZ], outiface_mask[IFNAMSIZ];
struct in_addr src, dst;
/*
来源
/
目的地址
*/
struct in_addr smsk,
dmsk;
/*
来源
/
目的地址的掩码
*/
-
-
-
-
-
-
-
-
-
上一篇:防火墙功能技术与实现
下一篇:接口设计规范V1.0___参考