目录
1 filter 表初始化 iptable_filter_init()
2 filter表的注册 iptable_filter_net_init()
2.1.1 static struct xt_table packet_filter
2.1.3 struct ipt_replace repl 初始化 ipt_alloc_initial_table()
3 filter表规则的添加 / 删除 do_replace()
4 filter表注册钩子函数 iptable_filter_hook()
5 扩展match、target注册 xt_register_match() / xt_register_target()
1 filter 表初始化 iptable_filter_init()
初始化函数即模块初始化函数 iptable_filter_init(),干了两件重要的事情:
- 注册filter表
- 注册三个钩子
//注册filter表
static int __net_init iptable_filter_net_init(struct net *net)
{
struct ipt_replace *repl;
repl = ipt_alloc_initial_table(&packet_filter);
if (repl == NULL)
return -ENOMEM;
/* Entry 1 is the FORWARD hook */
((struct ipt_standard *)repl->entries)[1].target.verdict =
forward ? -NF_ACCEPT - 1 : -NF_DROP - 1;
net->ipv4.iptable_filter =
ipt_register_table(net, &packet_filter, repl);
kfree(repl);
return PTR_RET(net->ipv4.iptable_filter);
}
static struct pernet_operations iptable_filter_net_ops = {
.init = iptable_filter_net_init,
.exit = iptable_filter_net_exit,
};
static int __init iptable_filter_init(void)
{
int ret;
ret = register_pernet_subsys(&iptable_filter_net_ops);
if (ret < 0)
return ret;
/* Register hooks */
//向Netfilter框架注册三个钩子:INPUT、OUTPUT、FORWARD
filter_ops = xt_hook_link(&packet_filter, iptable_filter_hook);
if (IS_ERR(filter_ops)) {
ret = PTR_ERR(filter_ops);
unregister_pernet_subsys(&iptable_filter_net_ops);
}
return ret;
}
2 filter表的注册 iptable_filter_net_init()
2.1 ipt_register_table() 入参
在看具体的实现之前,需要先来看看注册表时传入的两个参数:packet_filter 和 initial_table.repl。
2.1.1 static struct xt_table packet_filter
该结构正是Netfilter对table的定义
//filter表中的规则只能在LOCAL_IN、FORWARD、LOCAL_OUT三个点工作
#define FILTER_VALID_HOOKS ((1 << NF_INET_LOCAL_IN) | \
(1 << NF_INET_FORWARD) | \
(1 << NF_INET_LOCAL_OUT))
//packet_filter就是Netfilter框架定义的表结构struct xt_table对象,这里表名字为filter
static struct xt_table packet_filter = {
.name = "filter",
.valid_hooks = FILTER_VALID_HOOKS,
.lock = RW_LOCK_UNLOCKED,
.me = THIS_MODULE,
.af = AF_INET,
};
2.1.2 struct ipt_replace repl
和注册相关的核心数据结构是struct ipt_replace。iptables工具在更新规则时实际上是整体替换的,即先从内核dump所有规则,然后修改,最后整体替换,并非单独修改某一条规则。注册也可以理解为是一种替换,只是要替换表原本不存在而已,所以只要设计上面在某些地方做些许兼容,注册和替换的代码逻辑是可以复用的。而struct ipt_replace结构就是AF_INET协议族专门为了实现table替换而定义的辅助结构。
/* The argument to IPT_SO_SET_REPLACE. */
struct ipt_replace
{
char name[IPT_TABLE_MAXNAMELEN];
/* Which hook entry points are valid: bitmask. You can't change this. */
unsigned int valid_hooks;
/* Number of entries */
unsigned int num_entries;
/* Total size of new entries */
unsigned int size;
/* Hook entry points. */
unsigned int hook_entry[NF_INET_NUMHOOKS];
/* Underflow points. */
unsigned int underflow[NF_INET_NUMHOOKS];
/* Information about old entries: */
/* Number of counters (must be equal to current number of entries). */
unsigned int num_counters;
/* The old entries' counters. */
struct xt_counters __user *counters;
//末尾指针很重要,指向规则,对于initial_table来说就是repl后面的4条规则
struct ipt_entry entries[0];
};
如《linux内核协议栈 netfilter 之 ip 层的table、rule、match、target结构分析》中关于struct xt_table和struct xt_table_info的介绍,可以发现struct ipt_replace差不多是这两个结构的综合,而且它们共同的字段的含义确实是一致的。
2.1.3 struct ipt_replace repl 初始化 ipt_alloc_initial_table()
这个接口调用 xt_alloc_initial_table() 把repl初始化成如下值(linux2.6.21老版全局初始化):
repl = {
.name = "filter",
.valid_hooks = FILTER_VALID_HOOKS,
//初始化时有4条规则,如上,initial_table中在repl后面定义了4条规则
.num_entries = 4,
//size大小就是所有规则大小的累加
.size = sizeof(struct ipt_standard) * 3 + sizeof(struct ipt_error),
//因为hook_entry[]和underflow[]分别记录的是该表在每个hook点上的第一条和
//最后一条规则距离表起点的偏移量,并且当前表中每个hook点只有一条规则,所以
//它们的初始化值都是相同的
.hook_entry = {
[NF_INET_LOCAL_IN] = 0,
[NF_INET_FORWARD] = sizeof(struct ipt_standard),
[NF_INET_LOCAL_OUT] = sizeof(struct ipt_standard) * 2,
},
.underflow = {
[NF_INET_LOCAL_IN] = 0,
[NF_INET_FORWARD] = sizeof(struct ipt_standard),
[NF_INET_LOCAL_OUT] = sizeof(struct ipt_standard) * 2,
},
//下面是对默认的4条规则的初始化。虽然entries和term并不属于repl,但是因为它们
//在内存上面是连续的,所以可以使用这种方式进行初始化,内核中有很多这样的小技巧
//三个hook点各放一条无条件接受的规则。这三条规则实际上就是hook点的策略规则,
//它们会永远位于给hook点上所有规则的末尾,当其它规则都不匹配时,交由该规则
//觉得数据包最后的命运
.entries = {
IPT_STANDARD_INIT(NF_ACCEPT), /* LOCAL_IN */
IPT_STANDARD_INIT(NF_ACCEPT), /* FORWARD */
IPT_STANDARD_INIT(NF_ACCEPT), /* LOCAL_OUT */
},
//这里放置了一条错误规则。个人理解:这条规则应该永远都不会被遍历到,因为在遍历
//规则时是根据hook点遍历的,而hook点规则由hook_entry[]和underflow[]定界,永远
//都不应该走到这里,放着这么一条应该是为了对代码异常场景提供一种保护
.term = IPT_ERROR_INIT, /* ERROR */
},
2.2 ipt_register_table() 【核心】
表的注册并不是直接调用Netfilter框架提供的xt_register_table(),AF_INET协议族对注册过程有特殊处理,所以对注册过程用ipt_register_table()进行了封装。
struct xt_table *ipt_register_table(struct net *net,
const struct xt_table *table,
const struct ipt_replace *repl)
{
int ret;
struct xt_table_info *newinfo;
//该结构就是为了复用表替换代码而虚构出来的表
struct xt_table_info bootstrap = {0};
void *loc_cpu_entry;
struct xt_table *new_table;
//按照规则大小分配struct xt_table_info结构
//还需要申请一块内存块用于存放所有的规则
//即:分别申请sizeof(xt_table_info)与repl->size两个内存块。
newinfo = xt_alloc_table_info(repl->size);
if (!newinfo) {
ret = -ENOMEM;
goto out;
}
/* choose the copy on our node/cpu, but dont care about preemption */
//将repl末尾保存的规则拷贝到struct xt_table_info中本地CPU的指针位置
loc_cpu_entry = newinfo->entries[raw_smp_processor_id()];
memcpy(loc_cpu_entry, repl->entries, repl->size);
//用指定的信息检查新table中的规则信息(newinfo指向)
ret = translate_table(net, newinfo, loc_cpu_entry, repl);
if (ret != 0)
goto out_free;
//将该xt_table插入到net->xt[pf].tables链表中,并将指针table->private指向newinfo
new_table = xt_register_table(net, table, &bootstrap, newinfo);
if (IS_ERR(new_table)) {
ret = PTR_ERR(new_table);
goto out_free;
}
return new_table;
...
}
2.2.1 xt_alloc_table_info()
该函数的作用就是申请一个xt_table_info,然后根据传递的size值,为指针数组xt_table_info.entries[num_cpu]中的每一个指针申请size大小的内存空间,用于存放规则链中的所有规则。
功能:申请一个新的xt_table_info结构大小的内存,且根据size的大小,为xt_table_info->entries[cpu]数组的每一个成员申请size大小的内存(即为每一个cpu都申请一个size大小的内存)
size:一个xt_table_info在每一个cpu中所对应的的规则数的总内存。
struct xt_table_info *xt_alloc_table_info(unsigned int size)
{
struct xt_table_info *newinfo;
int cpu;
/* Pedantry: prevent them from hitting BUG() in vmalloc.c --RR */
if ((SMP_ALIGN(size) >> PAGE_SHIFT) + 2 > totalram_pages)
return NULL;
newinfo = kzalloc(XT_TABLE_INFO_SZ, GFP_KERNEL);
if (!newinfo)
return NULL;
newinfo->size = size;
for_each_possible_cpu(cpu) {
if (size <= PAGE_SIZE)
newinfo->entries[cpu] = kmalloc_node(size,
GFP_KERNEL,
cpu_to_node(cpu));
else
newinfo->entries[cpu] = vmalloc_node(size,
cpu_to_node(cpu));
if (newinfo->entries[cpu] == NULL) {
xt_free_table_info(newinfo);
return NULL;
}
}
return newinfo;
}
EXPORT_SYMBOL(xt_alloc_table_info);
2.2.2 translate_table()【核心】
如注释所述,该函数的作用就是用调用者指定的信息校验并初始化newinfo,其实就是对要注册的表内容进行合法性检查。该函数的核心逻辑如下:
- 初始化newinfo->hook_entry[]、newinfo->underflow[]
- 调用 xt_entry_foreach,遍历从entry0开始的所有ipt_entry,对每一个ipt_entry调用函数 check_entry_size_and_hooks(),判断其首地址边界对其是否正确、该ipt_entry的大小是否超过了最大值、当前ipt_entry相对于首个ipt_entry首地址的偏移量是否与某一个newinfo->hook_entry[]的值相等。
- 调用mark_source_chains() 链中的规则是否存在检查环路
- 调用 xt_entry_foreach,遍历从entry0开始的所有ipt_entry,对每一个ipt_entry调用函数 find_check_entry(),对该规则对应的match与target结构进行设置。
- 若以上3步检查均通过,则将该cpu对应的entriers指向的内存空间的内容,拷贝到其它cpu的entries指针指向的内存空间中。
说明:这里的entry0指的是repl->entries初始化值
/* Checks and translates the user-supplied table segment (held in
newinfo) */
static int
translate_table(struct net *net, struct xt_table_info *newinfo, void *entry0,
const struct ipt_replace *repl)
{
struct ipt_entry *iter;
unsigned int i;
int ret = 0;
//规则总的占用字节数和规则数目
newinfo->size = repl->size;
newinfo->number = repl->num_entries;
/* Init all hooks to impossible value. */
for (i = 0; i < NF_INET_NUMHOOKS; i++) {
newinfo->hook_entry[i] = 0xFFFFFFFF;
newinfo->underflow[i] = 0xFFFFFFFF;
}
duprintf("translate_table: size %u\n", newinfo->size);
i = 0;
//1.检查规则内的偏移量成员指定的是否准确
/* Walk through entries, checking offsets. */
xt_entry_foreach(iter, entry0, newinfo->size) {
ret = check_entry_size_and_hooks(iter, newinfo, entry0,
entry0 + repl->size,
repl->hook_entry,
repl->underflow,
repl->valid_hooks);
if (ret != 0)
return ret;
++i;
if (strcmp(ipt_get_target(iter)->u.user.name,
XT_ERROR_TARGET) == 0)
++newinfo->stacksize;
}
if (i != repl->num_entries) {
duprintf("translate_table: %u not %u entries\n",
i, repl->num_entries);
return -EINVAL;
}
//在上一步的检测过程中如果规则检测pass,会对hook_entry和underflow赋值,
//这里检查是否所有有效的HOOK点都已经指定了合理的值
/* Check hooks all assigned */
for (i = 0; i < NF_INET_NUMHOOKS; i++) {
/* Only hooks which are valid */
if (!(repl->valid_hooks & (1 << i)))
continue;
if (newinfo->hook_entry[i] == 0xFFFFFFFF) {
duprintf("Invalid hook entry %u %u\n",
i, repl->hook_entry[i]);
return -EINVAL;
}
if (newinfo->underflow[i] == 0xFFFFFFFF) {
duprintf("Invalid underflow %u %u\n",
i, repl->underflow[i]);
return -EINVAL;
}
}
//2.检查表中的规则是否构成环路
if (!mark_source_chains(newinfo, repl->valid_hooks, entry0))
return -ELOOP;
//3.无论是match还是target,都有对应的check回调,这里检查每条规则的match和target
/* Finally, each sanity check must pass */
i = 0;
xt_entry_foreach(iter, entry0, newinfo->size) {
ret = find_check_entry(iter, net, repl->name, repl->size);
if (ret != 0)
break;
++i;
}
if (ret != 0) {
xt_entry_foreach(iter, entry0, newinfo->size) {
if (i-- == 0)
break;
cleanup_entry(iter, net);
}
return ret;
}
//4.通过了所有的检查,将规则信息拷贝到其它CPU的内存中
/* And one copy for every other CPU */
for_each_possible_cpu(i) {
if (newinfo->entries[i] && newinfo->entries[i] != entry0)
memcpy(newinfo->entries[i], entry0, newinfo->size);
}
return ret;
}
2.2.3 xt_register_table()
该函数实现xt_table插入到xt[table->af].tables链表,并更新xt_table->private指针,具体实施核心步骤如下:
- 获取xt[table->af].mutex信号量
- 调用list_named_find,查找新的xt_table是否已存在xt[table->af].tables链表中,若存在,则返回存在标志;若不存在,则执行3
- 调用xt_replace_table,替换xt_table中的private
- 更新新的xt_table->private->initial_entries的值
- 将该xt_table插入到xt[table->af].tables链表中
struct xt_table *xt_register_table(struct net *net,
const struct xt_table *input_table,
struct xt_table_info *bootstrap,
struct xt_table_info *newinfo)
{
int ret;
struct xt_table_info *private;
struct xt_table *t, *table;
/* Don't add one object to multiple lists. */
//将参数指定的table拷贝一份
table = kmemdup(input_table, sizeof(struct xt_table), GFP_KERNEL);
if (!table) {
ret = -ENOMEM;
goto out;
}
//如上所述,全局数组xt[table->af]保存的该协议族的所有match和target,
//但是这里持锁后访问了net->xt.tables[table->af]变量,该变量保存的是
//该协议族所有的table。这把锁的定义位置并不合适,其本意是要保护table、
//match、target,但是table的定义却在net中(更高版本已将match和target
//也移到了net中)
ret = mutex_lock_interruptible(&xt[table->af].mutex);
if (ret != 0)
goto out_free;
//检查该协议族的已定义table中是否已有指定名字的table,可见表名字需要协议族内部唯一
/* Don't autoload: we'd eat our tail... */
list_for_each_entry(t, &net->xt.tables[table->af], list) {
if (strcmp(t->name, table->name) == 0) {
ret = -EEXIST;
goto unlock;
}
}
/* Simplifies replace_table code. */
table->private = bootstrap;
//用newinfo替换table中原来的private指针
if (!xt_replace_table(table, 0, newinfo, &ret))
goto unlock;
private = table->private;
pr_debug("table->private->number = %u\n", private->number);
/* save number of initial entries */
private->initial_entries = private->number;
//将table加入到该协议族的table集合中,注册完毕
list_add(&table->list, &net->xt.tables[table->af]);
mutex_unlock(&xt[table->af].mutex);
return table;
unlock:
mutex_unlock(&xt[table->af].mutex);
out_free:
kfree(table);
out:
return ERR_PTR(ret);
}
3 filter表规则的添加 / 删除 do_replace()
当用户层通过iptables -A添加一条规则或者通过iptables -D删除一条规则时,最终都会调用函数do_replace函数实现新的规则的添加或者规则的删除(其实就是替换xt_table->private,即需要创建一个新的xt_table_info和一个新的内存块,用于存放新的所有的规则)。
do_replace的定义如下,主要就是拷贝用户侧传递的ipt_replace值(注册新的xt_table时,也是传递ipt_replace的变量);然后调用函数xt_alloc_table_info申请内存空间;接着调用函数translate_table对新申请的内存空间中的每一个规则进行设置操作;最后调用xt_replace_table替换xt_table->private。
与表的注册函数相比,少了将xt_table插入到 net->xt.tables[table->af] 链表的步骤,其他的与表注册大致是相同的。
static int
do_replace(struct net *net, const void __user *user, unsigned int len)
{
int ret;
struct ipt_replace tmp;
struct xt_table_info *newinfo;
void *loc_cpu_entry;
struct ipt_entry *iter;
if (copy_from_user(&tmp, user, sizeof(tmp)) != 0)
return -EFAULT;
/* overflow check */
if (tmp.num_counters >= INT_MAX / sizeof(struct xt_counters))
return -ENOMEM;
tmp.name[sizeof(tmp.name)-1] = 0;
newinfo = xt_alloc_table_info(tmp.size);
if (!newinfo)
return -ENOMEM;
/* choose the copy that is on our node/cpu */
loc_cpu_entry = newinfo->entries[raw_smp_processor_id()];
if (copy_from_user(loc_cpu_entry, user + sizeof(tmp),
tmp.size) != 0) {
ret = -EFAULT;
goto free_newinfo;
}
ret = translate_table(net, newinfo, loc_cpu_entry, &tmp);
if (ret != 0)
goto free_newinfo;
duprintf("Translated table\n");
ret = __do_replace(net, tmp.name, tmp.valid_hooks, newinfo,
tmp.num_counters, tmp.counters);
if (ret)
goto free_newinfo_untrans;
return 0;
free_newinfo_untrans:
xt_entry_foreach(iter, loc_cpu_entry, newinfo->size)
cleanup_entry(iter, net);
free_newinfo:
xt_free_table_info(newinfo);
return ret;
}
4 filter表注册钩子函数 iptable_filter_hook()
通过上面的分析我们知道,我们通过iptables -A操作添加的规则,都会保存在一个xt_table->private->entries[]中,所以当数据到来后,协议栈执行NF_HOOK操作时,肯定需要遍历xt_table->private->entries中的规则,找到一个匹配的规则,然后对进来的数据包执行该规则的target操作。从xt_table的通用性,我们可以猜到,肯定会有一个通用的表规则匹配函数接口,被filter、nat、mangle表的hook回调函数调用。那这个函数是谁呢?这个函数就是iptable_filter_hook()。这里可以回顾一下在《linux内核协议栈 netfilter 之 ip 层的 hook 注册概述以及 hook 的调用场景》中介绍到filter表的三个钩子注册是通过调用xt_hook_link()接口进行批量注册,且三个钩子的钩子函数均为 iptable_filter_hook()。
static unsigned int
iptable_filter_hook(unsigned int hook, struct sk_buff *skb,
const struct net_device *in, const struct net_device *out,
int (*okfn)(struct sk_buff *))
{
const struct net *net;
if (hook == NF_INET_LOCAL_OUT &&
(skb->len < sizeof(struct iphdr) ||
ip_hdrlen(skb) < sizeof(struct iphdr)))
/* root is playing with raw sockets. */
return NF_ACCEPT;
net = dev_net((in != NULL) ? in : out);
return ipt_do_table(skb, hook, in, out, net->ipv4.iptable_filter);
}
4.1 ipt_do_table()
该函数的功能为遍历xt_table表中相应默认规则链里的所有规则,找到一个匹配的规则,并返回target结果。
/* Returns one of the generic firewall policies, like NF_ACCEPT. */
unsigned int
ipt_do_table(struct sk_buff *skb,
unsigned int hook,
const struct net_device *in,
const struct net_device *out,
struct xt_table *table)
{
static const char nulldevname[IFNAMSIZ] __attribute__((aligned(sizeof(long))));
const struct iphdr *ip;
/* Initializing verdict to NF_DROP keeps gcc happy. */
unsigned int verdict = NF_DROP;
const char *indev, *outdev;
const void *table_base;
struct ipt_entry *e, **jumpstack;
unsigned int *stackptr, origptr, cpu;
const struct xt_table_info *private;
struct xt_action_param acpar;
unsigned int addend;
/* Initialization */
ip = ip_hdr(skb);
indev = in ? in->name : nulldevname;
outdev = out ? out->name : nulldevname;
/* We handle fragments by dealing with the first fragment as
* if it was a normal packet. All other fragments are treated
* normally, except that they will NEVER match rules that ask
* things we don't know, ie. tcp syn flag or ports). If the
* rule is also a fragment-specific rule, non-fragments won't
* match it. */
acpar.fragoff = ntohs(ip->frag_off) & IP_OFFSET;
acpar.thoff = ip_hdrlen(skb);
acpar.hotdrop = false;
acpar.in = in;
acpar.out = out;
acpar.family = NFPROTO_IPV4;
acpar.hooknum = hook;
IP_NF_ASSERT(table->valid_hooks & (1 << hook));
local_bh_disable();
addend = xt_write_recseq_begin();
private = table->private;
cpu = smp_processor_id();
table_base = private->entries[cpu];
jumpstack = (struct ipt_entry **)private->jumpstack[cpu];
stackptr = per_cpu_ptr(private->stackptr, cpu);
origptr = *stackptr;
/*获取到相应规则链的首个规则*/
e = get_entry(table_base, private->hook_entry[hook]);
pr_debug("Entering %s(hook %u); sp at %u (UF %p)\n",
table->name, hook, origptr,
get_entry(table_base, private->underflow[hook]));
do {
const struct xt_entry_target *t;
const struct xt_entry_match *ematch;
IP_NF_ASSERT(e);
//ip_packet_match()对skb进行基本条件匹配,如果ip地址、网卡
if (!ip_packet_match(ip, indev, outdev,
&e->ip, acpar.fragoff)) {
no_match:
//不匹配,继续下一条规则
e = ipt_next_entry(e);
continue;
}
//扩展匹配
xt_ematch_foreach(ematch, e) {
acpar.match = ematch->u.kernel.match;
acpar.matchinfo = ematch->data;
//直接调用扩展匹配的match()回调函数,这里注册是icmp_match()
if (!acpar.match->match(skb, &acpar))
goto no_match;
}
//所有匹配都已命中,累加计数器
ADD_COUNTER(e->counters, skb->len, 1);
//获取本条规则的target
t = ipt_get_target(e);
IP_NF_ASSERT(t->u.kernel.target);
#if defined(CONFIG_NETFILTER_XT_TARGET_TRACE) || \
defined(CONFIG_NETFILTER_XT_TARGET_TRACE_MODULE)
/* The packet is traced: log it */
if (unlikely(skb->nf_trace))
trace_packet(skb, hook, in, out,
table->name, private, e);
#endif
/* Standard target? */
if (!t->u.kernel.target->target) {
int v;
//xt_target中的target回调函数指针为空表示是标准target
v = ((struct xt_standard_target *)t)->verdict;
if (v < 0) {
/* Pop from stack? */
//verdict小于0,并且不是 XT_RETURN,说明属于ACCEPT或者DROP,
//这已经是一种判决结果了,结束整个表在该HOOK点的处理过程
if (v != XT_RETURN) {
verdict = (unsigned int)(-v) - 1;
break;
}
if (*stackptr <= origptr) {
//verdict是 XT_RETURN ,说明是自定义链的返回。
//这里,指针back和comefrom实现了一种类似于栈的操作,back为栈顶元素
//栈中的每个元素用规则中的comefrom指向
e = get_entry(table_base,
private->underflow[hook]);
pr_debug("Underflow (this is normal) "
"to %p\n", e);
} else {
e = jumpstack[--*stackptr];
pr_debug("Pulled %p out from pos %u\n",
e, *stackptr);
e = ipt_next_entry(e);
}
continue;
}
//target是自定义链,back入栈,然后跳转到自定义链的规则入口
if (table_base + v != ipt_next_entry(e) &&
!(e->ip.flags & IPT_F_GOTO)) {
if (*stackptr >= private->stacksize) {
verdict = NF_DROP;
break;
}
jumpstack[(*stackptr)++] = e;
pr_debug("Pushed %p into pos %u\n",
e, *stackptr - 1);
}
//对于自定义链,v大于0,保存的是该自定义链距离table中第一条规则的偏移
e = get_entry(table_base, v);
continue;
}
acpar.target = t->u.kernel.target;
acpar.targinfo = t->data;
//这是一个扩展target,直接调用其target()回调
verdict = t->u.kernel.target->target(skb, &acpar);
/* Target might have changed stuff. */
ip = ip_hdr(skb);
//如果target的返回结果是 XT_CONTINUE,那么会继续遍历下一条,
//否则结束整个table的变量
if (verdict == XT_CONTINUE)
e = ipt_next_entry(e);
else
/* Verdict */
break;
//如果中间有match设置了hotdrop为true,那么可以提前结束遍历
} while (!acpar.hotdrop);
pr_debug("Exiting %s; resetting sp from %u to %u\n",
__func__, *stackptr, origptr);
*stackptr = origptr;
xt_write_recseq_end(addend);
local_bh_enable();
#ifdef DEBUG_ALLOW_ALL
return NF_ACCEPT;
#else
//hotdrop设置为true,会最终导致数据包被丢弃
if (acpar.hotdrop)
return NF_DROP;
else return verdict;
#endif
}
4.1.1 基本匹配ip_packet_match()
该函数根据skb头部和struct ipt_entry中的struct ipt_ip结构进行比较,基本意思就是逐个比较。具体是通过自定义的宏FWINV,巧妙的处理了ip地址匹配、协议号匹配、接口名称匹配与取反匹配双重判断后的匹配结果。这个宏设计的很巧妙。
/* Returns whether matches rule or not. */
/* Performance critical - called for every packet */
static inline bool
ip_packet_match(const struct iphdr *ip,
const char *indev,
const char *outdev,
const struct ipt_ip *ipinfo,
int isfrag)
{
unsigned long ret;
#define FWINV(bool, invflg) ((bool) ^ !!(ipinfo->invflags & (invflg)))
/*
下面这句话的意思是:
当到达分组的src ip地址经过掩码处理后与规则的src ip不等时,
且没有对src ip进行取反操作后,说明match不匹配,返回0
当到达分组的src ip地址经过掩码处理后与规则的src相等时,
其有对src ip地址进行取反操作后,说明match不匹配,返回0
当到达分组的dst ip地址经过掩码处理后与规则的dst ip不等时,
且没有对dst ip进行取反操作后,说明match不匹配,返回0
当到达分组的src ip地址经过掩码处理后与规则的dst ip相等时,
其有对dst ip地址进行取反操作后,说明match不匹配,返回0
*/
if (FWINV((ip->saddr&ipinfo->smsk.s_addr) != ipinfo->src.s_addr,
IPT_INV_SRCIP) ||
FWINV((ip->daddr&ipinfo->dmsk.s_addr) != ipinfo->dst.s_addr,
IPT_INV_DSTIP)) {
dprintf("Source or dest mismatch.\n");
dprintf("SRC: %pI4. Mask: %pI4. Target: %pI4.%s\n",
&ip->saddr, &ipinfo->smsk.s_addr, &ipinfo->src.s_addr,
ipinfo->invflags & IPT_INV_SRCIP ? " (INV)" : "");
dprintf("DST: %pI4 Mask: %pI4 Target: %pI4.%s\n",
&ip->daddr, &ipinfo->dmsk.s_addr, &ipinfo->dst.s_addr,
ipinfo->invflags & IPT_INV_DSTIP ? " (INV)" : "");
return false;
}
ret = ifname_compare_aligned(indev, ipinfo->iniface, ipinfo->iniface_mask);
if (FWINV(ret != 0, IPT_INV_VIA_IN)) {
dprintf("VIA in mismatch (%s vs %s).%s\n",
indev, ipinfo->iniface,
ipinfo->invflags&IPT_INV_VIA_IN ?" (INV)":"");
return false;
}
ret = ifname_compare_aligned(outdev, ipinfo->outiface, ipinfo->outiface_mask);
if (FWINV(ret != 0, IPT_INV_VIA_OUT)) {
dprintf("VIA out mismatch (%s vs %s).%s\n",
outdev, ipinfo->outiface,
ipinfo->invflags&IPT_INV_VIA_OUT ?" (INV)":"");
return false;
}
/* Check specific protocol */
if (ipinfo->proto &&
FWINV(ip->protocol != ipinfo->proto, IPT_INV_PROTO)) {
dprintf("Packet protocol %hi does not match %hi.%s\n",
ip->protocol, ipinfo->proto,
ipinfo->invflags&IPT_INV_PROTO ? " (INV)":"");
return false;
}
/* If we have a fragment rule but the packet is not a fragment
* then we return zero */
if (FWINV((ipinfo->flags&IPT_F_FRAG) && !isfrag, IPT_INV_FRAG)) {
dprintf("Fragment rule but not fragment.%s\n",
ipinfo->invflags & IPT_INV_FRAG ? " (INV)" : "");
return false;
}
return true;
}
5 扩展match、target注册 xt_register_match() / xt_register_target()
static struct xt_match ipt_builtin_mt[] __read_mostly = {
{
.name = "icmp",
.match = icmp_match,
.matchsize = sizeof(struct ipt_icmp),
.checkentry = icmp_checkentry,
.proto = IPPROTO_ICMP,
.family = NFPROTO_IPV4,
},
};
static struct xt_target ipt_builtin_tg[] __read_mostly = {
{
.name = XT_STANDARD_TARGET,
.targetsize = sizeof(int),
.family = NFPROTO_IPV4,
#ifdef CONFIG_COMPAT
.compatsize = sizeof(compat_int_t),
.compat_from_user = compat_standard_from_user,
.compat_to_user = compat_standard_to_user,
#endif
},
{
.name = XT_ERROR_TARGET,
.target = ipt_error,
.targetsize = XT_FUNCTION_MAXNAMELEN,
.family = NFPROTO_IPV4,
},
};
static int __init ip_tables_init(void)
{
int ret;
ret = register_pernet_subsys(&ip_tables_net_ops);
if (ret < 0)
goto err1;
/* No one else will be downing sem now, so we won't sleep */
ret = xt_register_targets(ipt_builtin_tg, ARRAY_SIZE(ipt_builtin_tg));
if (ret < 0)
goto err2;
ret = xt_register_matches(ipt_builtin_mt, ARRAY_SIZE(ipt_builtin_mt));
if (ret < 0)
goto err4;
/* Register setsockopt */
ret = nf_register_sockopt(&ipt_sockopts);
if (ret < 0)
goto err5;
pr_info("(C) 2000-2006 Netfilter Core Team\n");
return 0;
err5:
xt_unregister_matches(ipt_builtin_mt, ARRAY_SIZE(ipt_builtin_mt));
err4:
xt_unregister_targets(ipt_builtin_tg, ARRAY_SIZE(ipt_builtin_tg));
err2:
unregister_pernet_subsys(&ip_tables_net_ops);
err1:
return ret;
}
6 小结(重要)
在《linux内核协议栈 netfilter 之 ip 层的table、rule、match、target结构分析》中分析数据结构时,我们应该有注意到一个细节,即xt_table_info->entries[] 是指向每一个CPU对应于该 xt_table 的所有规则的首地址,而一个表里面所有的规则是按照连续内存存取的。由于表里的规则是存储在连续内存里,那么将一个新的规则插入到一个规则链中就变得比较麻烦,所以现有的做法基本上是重新申请一块内存,根据应用层传递过来的新的规则分布,对新的内存进行规则的赋值等操作,最后将原有规则内存释放掉。