http://damocles.ycool.com/post.2701550.htmllinux防火墙netfilter的代码分析Damocles 发表于 2007-08-25 08:54:23从图1和图2中我们可以看清楚linux下防火墙netfilter的运作方式,设有5个钩子函数
不同的模块将会注册不同的钩子函数,每当数据包处理到这个阶段时,就调用模块中定义的处理函数,而netfilter提供了一个整体框架,避免了原来代码 的杂乱无章。如图,当数据包从外部进来,首先处理的是PRE_ROUTING,然后转到路由,路由判断究竟是forward这个数据包还是读入本机处理, 如果是转发的,会进入到FORWARD钩子,如果是读入处理的,会到LOCAL_IN钩子。当数据包是从本机发出的数据包时,经过LOCAL_OUT钩 子,然后路由判断。最后FORWARD和LOCAL_OUT都会经过POST_ROUTING钩子。这样整个防火墙的结构就很清晰了,主要由INPUT OUTPUT FORWARD三个链条组成。 ![]() 图2 netfilter处理流程 我们通过源代码来看一下它的实现。我们首先看一下发送ip数据包到上层的函数ip_local_deliver(),它所在的文件是 net/ipv4/ip_input.c 261 /*
262 * Deliver IP Packets to the higher protocol layers.
263 */
264 int ip_local_deliver(struct sk_buff *skb)
265 {
266 /*
267 * Reassemble IP fragments.
268 */
269
270 if (skb->nh.iph->frag_off & htons(IP_MF|IP_OFFSET)) {
271 skb = ip_defrag(skb, IP_DEFRAG_LOCAL_DELIVER);
272 if (!skb)
273 return 0;
274 }
275
276 return NF_HOOK(PF_INET, NF_IP_LOCAL_IN, skb, skb->dev, NULL,
277 ip_local_deliver_finish);
278 }
我们关心其中的那个returen语句,调用了函数NF_HOOK,这个自然就是netfilter的hook调用。我们发现这其实是一个宏,那么继续深
入下去看看:
246 #define NF_HOOK(pf, hook, skb, indev, outdev, okfn) \ 247 NF_HOOK_THRESH(pf, hook, skb, indev, outdev, okfn, INT_MIN)可见它的形参分别是协议类型,钩子类型,skb,进去的device,出去的device以及回调函数指针,它首先会探寻说我们的规则表中有没有对这类情 况注册钩子函数进行匹配处理,如果有的话,会调用钩子函数,如果没有的话,则继续执行形参中的回调函数,完成整个过程。可见netfilter是一个很轻 量级的,和内核网络代码能轻易剥离的防火墙。我们继续往下看: 182 /**
183 * nf_hook_thresh - call a netfilter hook
184 *
185 * Returns 1 if the hook has allowed the packet to pass. The function
186 * okfn must be invoked by the caller in this case. Any other return
187 * value indicates the packet has been consumed by the hook.
188 */
189 static inline int nf_hook_thresh(int pf, unsigned int hook,
190 struct sk_buff **pskb,
191 struct net_device *indev,
192 struct net_device *outdev,
193 int (*okfn)(struct sk_buff *), int thresh,
194 int cond)
195 {
196 if (!cond)
197 return 1;
198 #ifndef CONFIG_NETFILTER_DEBUG
199 if (list_empty(&nf_hooks[pf][hook]))
200 return 1;
201 #endif
202 return nf_hook_slow(pf, hook, pskb, indev, outdev, okfn, thresh);
203 }
这里出现了一个非常重要的数据结构nf_hooks,我们去
看一下到底是怎么样子的58 struct list_head nf_hooks[NPROTO][NF_MAX_HOOKS]; 一 个很典型的二维数组,第一维是协议类型,第二维是一个协议最多的钩子函数的数量。这个数组的每一项就是一个list头,指向一串有钩子函数的链表,当这个 数组的这一项为空时,即没有钩子函数挂接时,函数nf_hook_thresh返回1,也就是直接执行okfn函数,否则的话继续调用 nf_hook_slow()。 我们来看一个nf_hooks初始化的例子,在net/ipv4/netfilter/iptable_filter.c中的初始化函数 142 static int __init iptable_filter_init(void)
143 {
144 int ret;
145
146 if (forward < 0 || forward > NF_MAX_VERDICT) {
147 printk("iptables forward must be 0 or 1\n");
148 return -EINVAL;
149 }
150
151 /* Entry 1 is the FORWARD hook */
152 initial_table.entries[1].target.verdict = -forward - 1;
153
154 /* Register table */
155 ret = ipt_register_table(&packet_filter, &initial_table.repl);
156 if (ret < 0)
157 return ret;
158
159 /* Register hooks */
160 ret = nf_register_hooks(ipt_ops, ARRAY_SIZE(ipt_ops));
161 if (ret < 0)
162 goto cleanup_table;
163
164 return ret;
165
166 cleanup_table:
167 ipt_unregister_table(&packet_filter);
168 return ret;
169 }
其中的注册table和注册钩子函数就很清晰了,这些都是在初始化时候完成的。我们继续看nf_register_hooks函数,它调用了
nf_register_hook函数。 62 int nf_register_hook(struct nf_hook_ops *reg)
63 {
64 struct list_head *i;
65
66 spin_lock_bh(&nf_hook_lock);
67 list_for_each(i, &nf_hooks[reg->pf][reg->hooknum]) {
68 if (reg->priority < ((struct nf_hook_ops *)i)->priority)
69 break;
70 }
71 list_add_rcu(®->list, i->prev);
72 spin_unlock_bh(&nf_hook_lock);
73
74 synchronize_net();
75 return 0;
76 }
这个函数很清楚了,它注册一个数据结构到nf_hook_ops的数据结构到表nf_hooks中的相应位置中去,在list中的位置根据reg的
priority的值,应该是数值越小,优先级越高,就越先处理。而nf_hook_ops的内容猜都能猜出来吧,肯定是钩子函数咯。我们回到前面的调用函数nf_hook_slow那个地方,看看这个函数究竟做什么的。 161 int nf_hook_slow(int pf, unsigned int hook, struct sk_buff **pskb,
162 struct net_device *indev,
163 struct net_device *outdev,
164 int (*okfn)(struct sk_buff *),
165 int hook_thresh)
166 {
167 struct list_head *elem;
168 unsigned int verdict;
169 int ret = 0;
170
171 /* We may already have this, but read-locks nest anyway */
172 rcu_read_lock();
173
174 elem = &nf_hooks[pf][hook];
175 next_hook:
176 verdict = nf_iterate(&nf_hooks[pf][hook], pskb, hook, indev,
177 outdev, &elem, okfn, hook_thresh);
178 if (verdict == NF_ACCEPT || verdict == NF_STOP) {
179 ret = 1;
180 goto unlock;
181 } else if (verdict == NF_DROP) {
182 kfree_skb(*pskb);
183 ret = -EPERM;
184 } else if ((verdict & NF_VERDICT_MASK) == NF_QUEUE) {
185 NFDEBUG("nf_hook: Verdict = QUEUE.\n");
186 if (!nf_queue(pskb, elem, pf, hook, indev, outdev, okfn,
187 verdict >> NF_VERDICT_BITS))
188 goto next_hook;
189 }
190 unlock:
191 rcu_read_unlock();
192 return ret;
193 }
我们发现一个变量verdict,这个就是钩子函数对数据包的处理结果,它有以下几种类型,NF_STOP我也不知道是干嘛的!!
1.是当接受和停止时,返回1 2.丢弃时释放内存空间 3.进队列则为加入队列然后继续下一个hook 我们可以看到其中的中心函数必定是nf_iterate,它将返回verdict给我们。 117 unsigned int nf_iterate(struct list_head *head,
118 struct sk_buff **skb,
119 int hook,
120 const struct net_device *indev,
121 const struct net_device *outdev,
122 struct list_head **i,
123 int (*okfn)(struct sk_buff *),
124 int hook_thresh)
125 {
126 unsigned int verdict;
127
128 /*
129 * The caller must not block between calls to this
130 * function because of risk of continuing from deleted element.
131 */
132 list_for_each_continue_rcu(*i, head) {
133 struct nf_hook_ops *elem = (struct nf_hook_ops *)*i;
134
135 if (hook_thresh > elem->priority)
136 continue;
137
138 /* Optimization: we don't need to hold module
139 reference here, since function can't sleep. --RR */
140 verdict = elem->hook(hook, skb, indev, outdev, okfn);
141 if (verdict != NF_ACCEPT) {
142 #ifdef CONFIG_NETFILTER_DEBUG
143 if (unlikely((verdict & NF_VERDICT_MASK)
144 > NF_MAX_VERDICT)) {
145 NFDEBUG("Evil return from %p(%u).\n",
146 elem->hook, hook);
147 continue;
148 }
149 #endif
150 if (verdict != NF_REPEAT)
151 return verdict;
152 *i = (*i)->prev;
153 }
154 }
155 return NF_ACCEPT;
156 }这下我们应该清楚了,这个迭代就是挨个运行nf_hooks[pf][hook]所指向链表中的钩子函数elem->hook。如果其中有一个钩子 函数没有ACCEPT且不是repeat,就直接跳出循环了,然后返回verdict,如果ACCEPT了,则继续处理下一个钩子函数,直到处理完。 60 struct nf_hook_ops
61 {
62 struct list_head list;
63
64 /* User fills in from here down. */
65 nf_hookfn *hook;
66 struct module *owner;
67 int pf;
68 int hooknum;
69 /* Hooks are ordered in ascending priority. */
70 int priority;
71 };
我
们看看这个数据结构,再想想前面注册hook时候的情景,应该明白了。它其中定义了pf和hooknum,指定了在nf_hooks表中的元素位
置,nf_hookfn则是现实的钩子函数,而priority则指定了它在这个链表中的位置,按照升序排列。那么nf_hookfn是什么时候指定的
呢?这个自然是和各个协议相关的。在net/ipv4/netfilter/iptable_filter.c中,我们看到这么一个赋值语句。114 static struct nf_hook_ops ipt_ops[] = {
115 {
116 .hook = ipt_hook,
117 .owner = THIS_MODULE,
118 .pf = PF_INET,
119 .hooknum = NF_IP_LOCAL_IN,
120 .priority = NF_IP_PRI_FILTER,
121 },
122 {
123 .hook = ipt_hook,
124 .owner = THIS_MODULE,
125 .pf = PF_INET,
126 .hooknum = NF_IP_FORWARD,
127 .priority = NF_IP_PRI_FILTER,
128 },
129 {
130 .hook = ipt_local_out_hook,
131 .owner = THIS_MODULE,
132 .pf = PF_INET,
133 .hooknum = NF_IP_LOCAL_OUT,
134 .priority = NF_IP_PRI_FILTER,
135 },
136 };
这是在ipv4的filter中预先注册的,我们知道还有预先注册的像nat和mangle,当然我们也可以自己写模块,实现这个hook函数。像这个例
子中,第一个的hook函数就是ipt_hook,属于的协议是ipv4,属于的钩子类型是LOCAL_IN。说实话ipt_hook以及之后调用的ipt_do_table我没有看懂,汗,谁看懂了交流一下吧,呵呵! 相关日志:
收藏: QQ
书签 del.icio.us
订阅: Google 抓虾
|

