LinuxSir.cn,穿越时空的Linuxsir!

 找回密码
 注册
搜索
热搜: shell linux mysql
查看: 3541|回复: 13

Netfilter 代码分析

[复制链接]
发表于 2004-6-21 21:56:11 | 显示全部楼层 |阅读模式
近一段时间为了熟悉Linux的 Netfiler框架,读了Linux2.4.20内核的网络安全部分的一些源代码,想与大家分享一下,希望大家多多指点!
这里假设大家对Netfilter有一定的了解!

  1. Nefilter细节实现分析
  2. Netfilter定义了协议栈中的检查点和检查点上引用的数据结构以及对这些数据
  3. 结引用的过程。首先看看在检查点上引用的数据结构,如图所示:

  4. [IMG]http://www.nsfocus.net/magazine_upload/42_001.jpg[/IMG]
  5.               图1 nf_hoo_ops数据结构的组织

  6. 图中ns_hook_ops就是在检查点上引用的结构。每个协议栈预先定义的8个链表数
  7. 组用于保存这些结构,这些链表与协议栈中的检查点一一对应。在实际的应用
  8. 中,这8个链表并不一定都被使用,比如在IPV4中,只定义了5个检查点,分别对应前5个链表。
  9. nf_hook_ops结构定义在netfilter.h中,如下:

  10. 44 struct nf_hook_ops
  11. 45 {
  12. 46         struct list_head list;
  13. 47
  14. 48         /* User fills in from here down。 */
  15. 49         nf_hookfn hook;                          /*  函数指针 */
  16. 50         int pf;                                 /*  结构对应的协议栈号*/
  17. 51         int hooknum;                        /*  结构对应的检查点号*/
  18. 52         /* Hooks are ordered in ascending priority。 */
  19. 53         int priority;
  20. 54 };
  21. 其中有必要先说明一下 其中的一些数据结构,
  22. struct list_head的结构在lish.h中定义,并且在其中定义了一个比较重要的宏:
  23. #define LIST_HEAD(name) struct list_head name = LIST_HEAD_INIT(name)。
  24. 在以后的分析中我们将看到其重要性。

  25. 18 struct list_head {
  26. 19         struct list_head *next, *prev;
  27. 20 };
  28. 21
  29. 22 #define LIST_HEAD_INIT(name) { &(name), &(name) }
  30. 23
  31. 24 #define LIST_HEAD(name) \
  32. 25         struct list_head name = LIST_HEAD_INIT(name)

  33. 然后在nf_hook_ops结构中定义了一个指针函数hook其具体说明将在后面的分析中提及。
  34. 这个结构由函数nf_register_hook注册到list_head链表上,链表的索引由结构
  35. 中hooknum指定。同一链表上的结构按优先值由小到大排列。在检查点上引用这
  36. 些结构时,以它们在链表上的先后顺序引用。其实在这里有一个重要的数组结
  37. 构:就是在netfilter.c中定义的全局数组,

  38. 47 struct list_head nf_hooks[NPROTO][NF_MAX_HOOKS];

  39. 说的清楚点就是:比如要在IPV4协议栈上的某一个检查点上注册一个
  40. nf_hook_opts的话,那么其pf即为: PF_INET ,hooknum就为检查点,如
  41. NF_IP_PRE_ROUTING,那么它在这个二维数组中的位置就为nf_hooks[PF_INET]
  42. [NF_IP_PRE_ROUTING]。
  43. 那么nf_register_hook是如何工作的呢?

  44. 60 int nf_register_hook(struct nf_hook_ops *reg)
  45. 61 {
  46. 62         struct list_head *i;
  47. 63
  48. 64         br_write_lock_bh(BR_NETPROTO_LOCK);
  49. 65         for (i = nf_hooks[reg->pf][reg->hooknum].next;
  50. 66              i != &nf_hooks[reg->pf][reg->hooknum];
  51. 67              i = i->next) {
  52. 68            if (reg->priority < ((struct nf_hook_ops *)i)->priority)
  53. 69                    break;
  54. 70         }
  55. 71         list_add(&reg->list, i->prev);
  56. 72         br_write_unlock_bh(BR_NETPROTO_LOCK);
  57. 73         return 0;
  58. 74 }

  59. 用通过把构造好的nf_hook_ops传给函数nf_register_hook,其通过一个循环把这
  60. 个结构根据其自身的优先级(priority)插入在nf_hooks[pf][hooknum]链表的相
  61. 应的地方。重要的操作就是for循环体中的内容,如果reg->priority的值小于链
  62. 表中当前的nf_hook_ops中的优先级时,就立刻跳出循环,执行后来的插入操作.
  63. 此处可以看出这个链表上的所有nf_hook_ops结构都是按结构中的priority的值
  64. 由小到大的排列,这就为以后的操作提供了方便.插入工作由函数list_add完成,
  65. 其实就是一个链表的插入过程。那么当nf_hooks数组链上的结构都已经注册好
  66. 了,那下一步该做些什么呢,而且上面提到了检查点,下面看检查点的定义。

  67. 我们先看看netfilter_ipv4.h中定义的都有哪些检查点。
  68. 39 /* IP Hooks */
  69. 40 /* After promisc drops, checksum checks。 */
  70. 41 #define NF_IP_PRE_ROUTING       0
  71. 42 /* If the packet is destined for this box。 */
  72. 43 #define NF_IP_LOCAL_IN          1
  73. 44 /* If the packet is destined for another interface。 */
  74. 45 #define NF_IP_FORWARD           2
  75. 46 /* Packets coming from a local process。 */
  76. 47 #define NF_IP_LOCAL_OUT         3
  77. 48 /* Packets about to hit the wire。 */
  78. 49 #define NF_IP_POST_ROUTING      4
  79. 50 #define NF_IP_NUMHOOKS          5           //检查点的数目
  80. 实质上检查点的意思就是说当有一个数据包经过此处的话,那么这个数据包先必
  81. 须在这些地方做一些检查,如果此检查点觉得没有问题,那么你就继续下面的行
  82. 程,如果有问题,将被丢弃,或一些其它的处理。这些检查点是由Netfilter框架加
  83. 在协议栈上的,如果在编译内核时,没有定义CONFIG_NETFILTER的话,那么在这些
  84. 地方将不对这些数据包做任何处理处理,直接交由后面的部分处理。如果定义了
  85. ,那么在这些检查点上就要做一些事情了。Netfilter框架使用了一个巧妙的技
  86. 巧,在最大程度上不影响本身的协议。使用NF_HOOK宏来定义这些检查点上的处理
  87. 过程,先来看看NF_HOOK宏:

  88. 122 #define NF_HOOK(pf, hook, skb, indev, outdev, okfn)  \
  89. 123                                        (list_empty(&nf_hooks[(pf)][(hook)])    \
  90. 124                                  ? (okfn)(skb)                                        \
  91. 125  : nf_hook_slow((pf), (hook), (skb), (indev), (outdev), (okfn)))

  92. 这里又用到了nf_hooks这个数组,首先看到当在nf_hooks[pf][hook]这个链表为
  93. 空时,即没有在这个链上用nf_register_hook函数注册任何一个nf_hook_ops结构
  94. 时,这个宏就会直接调用由okfn指向的函数做处理,这里先不管okfn指向了什么
  95. 地方,也就相当于在这里没有做任何的检查。另外一种情况就是注册了一些nf_hook_ops在这个链表上,那么就会调用下面的这各函数做一些处理,

  96. 128 int nf_hook_slow(int pf, unsigned int hook, struct sk_buff *skb,
  97. 129               struct net_device *indev, struct net_device *outdev,
  98. 130                  int (*okfn)(struct sk_buff *));
  99. 再看其定义:
  100. 449 int nf_hook_slow(int pf, unsigned int hook, struct sk_buff *skb,
  101. 450                  struct net_device *indev,
  102. 451                  struct net_device *outdev,
  103. 452                  int (*okfn)(struct sk_buff *))
  104. 453 {
  105. 454         struct list_head *elem;
  106. 455         unsigned int verdict;
  107. 456         int ret = 0;
  108. 457
  109. 458 /*This stopgap cannot be removed until all the hooks are audited。*/
  110. 459         if (skb_is_nonlinear(skb) && skb_linearize(skb, GFP_ATOMIC) != 0) {
  111. 460                 kfree_skb(skb);
  112. 461                 return -ENOMEM;
  113. 462         }
  114. 463         if (skb->ip_summed == CHECKSUM_HW) {
  115. 464                 if (outdev == NULL) {
  116. 465                         skb->ip_summed = CHECKSUM_NONE;
  117. 466                 } else {
  118. 467                         skb_checksum_help(skb);
  119. 468                 }
  120. 469         }
  121. 470
  122. 471         /* We may already have this, but read-locks nest anyway */
  123. 472         br_read_lock_bh(BR_NETPROTO_LOCK);
  124. 473
  125. 474 #ifdef CONFIG_NETFILTER_DEBUG
  126. 475         if (skb->nf_debug & (1 << hook)) {
  127. 476                 printk(“nf_hook: hook %i already set。\n”, hook);
  128. 477                 nf_dump_skb(pf, skb);
  129. 478         }
  130. 479         skb->nf_debug |= (1 << hook);
  131. 480 #endif
  132. 481
  133. 482         elem = &nf_hooks[pf][hook];
  134. 483         verdict = nf_iterate(&nf_hooks[pf][hook], &skb, hook, indev,
  135. 484                              outdev, &elem, okfn);
  136. 485         if (verdict == NF_QUEUE) {
  137. 486                 NFDEBUG(“nf_hook: Verdict = QUEUE。\n”);
  138. 487                 nf_queue(skb, elem, pf, hook, indev, outdev, okfn);
  139. 488         }
  140. 489
  141. 490         switch (verdict) {
  142. 491         case NF_ACCEPT:
  143. 492                 ret = okfn(skb);
  144. 493                 break;
  145. 494
  146. 495         case NF_DROP:
  147. 496                 kfree_skb(skb);
  148. 497                 ret = -EPERM;
  149. 498                 break;
  150. 499         }
  151. 500
  152. 501         br_read_unlock_bh(BR_NETPROTO_LOCK);
  153. 502         return ret;
  154. 503 }

  155. 在最后明显的地方我们就看到了一些重点,看到了一些关键字:NF_ACCEPT,
  156. NF_DROP,从字面的意思很好理解,并且这些值是由一个叫nf_iterate函数的返回
  157. 值,这个函数的执行过程也很简单,如下:
  158.   
  159. 339 static unsigned int nf_iterate(struct list_head *head,
  160. 340                                struct sk_buff **skb,
  161. 341                                int hook,
  162. 342                                const struct net_device *indev,
  163. 343                                const struct net_device *outdev,
  164. 344                                struct list_head **i,
  165. 345                                int (*okfn)(struct sk_buff *))
  166. 346 {
  167. 347         for (*i = (*i)->next; *i != head; *i = (*i)->next) {
  168. 348               struct nf_hook_ops *elem = (struct nf_hook_ops *)*i;
  169. 349              switch (elem->hook(hook, skb, indev, outdev, okfn)){
  170. 350                 case NF_QUEUE:
  171. 351                         return NF_QUEUE;
  172. 352
  173. 353                 case NF_STOLEN:
  174. 354                         return NF_STOLEN;
  175. 355
  176. 356                 case NF_DROP:
  177. 357                         return NF_DROP;
  178. 358
  179. 359                 case NF_REPEAT:
  180. 360                         *i = (*i)->prev;
  181. 361                         break;
  182. 362
  183. 363 #ifdef CONFIG_NETFILTER_DEBUG
  184. 364                 case NF_ACCEPT:
  185. 365                         break;
  186. 366
  187. 367                 default:
  188. 368                         NFDEBUG(“Evil return from %p(%u)。\n”,
  189. 369                                 elem->hook, hook);
  190. 370 #endif
  191. 371                 }
  192. 372         }
  193. 373         return NF_ACCEPT;
  194. 374 }

  195.         这里看到了一个for循环,其做的就是把这个协议栈上的这个HOOK点
  196. 上的所有定义的nf_hook_ops结构遍历一边,并且调用了这个结构中的函数hook指
  197. 针,它正是指向我们定义的函数,然后根据这个函数的返回值做进一步的处理,并
  198. 且这里有更多的返回值,而在nf_hook_slow函数中只处理了NF_DROP,NF_ACCEPT,
  199. NF_QUEUE。并且看到对不同的返回值,做了一些不同的处理,如果我们定义的函数
  200. 返回的是NF_QUEUE,NF_STOLEN,NF_DROP,就立刻返回,如果是NF_REPEAT 只是 改
  201. 变一下当前循环中的指针,然后将从新调用我们的注册的处理函数。然而只有所
  202. 有这个链表上的注册函数都返回NF_ACCEPT时,这个函数才会返回NF_ACCEPT。

  203. 14 /* Responses from hook functions。 */
  204. 15 #define NF_DROP 0
  205. 16 #define NF_ACCEPT 1
  206. 17 #define NF_STOLEN 2
  207. 18 #define NF_QUEUE 3
  208. 19 #define NF_REPEAT 4
  209. 20 #define NF_MAX_VERDICT NF_REPEAT

  210. 如果nf_hook_ops结构中我们定义的函数中只要有一个返回NF_DROP或NF_STOLEN
  211. 或NF_QUEUE,函数nf_iterate将立即将这个值返回给nf_hook_slow函数,而只有
  212. 当我们这个链表上所有nf_hook_ops结构中的hook函数都返回NF_ACCEPT时,
  213. nf_iterate函数才返回一个NF_ACCEPT给nf_hook_slow函数。
  214. 这里我们注意到一个问题,调用我们注册的函数时,是按顺序来取的,这里就是体
  215. 现nf_hook_ops结构中优先级的重要性的时候了,当在list_head链表中注册我们
  216. 的结构时,是根据其中的优先级来确定位置的,优先级高的处在链表的前面。也就
  217. 是说当优先级高的函数返回NF_DROP或NF_STOLEN或NF_QUEUE时,那么后面的函数
  218. 将不用做任何处理。只有返回NF_ACCEPT时,低优先级的函数才能做进一步的表
  219. 决。
  220. 在ns_hook_slow中判断nf_iterate的返回值,如果是NF_ACCEPT,则允许数据包
  221. 通过,并将数据包传递给协议栈中的下一个函数;如果是NF_DROP,则释放数据
  222. 包,协议栈流程中断;如果是NF_STOLEN,同样中断协议栈的流程,但是没有释
  223. 放这个数据包;如果是NF_QUEUE,则将这个包发送到用户空间处理,同时中断协
  224. 议栈的流程。现在我们来看看检查点在协议栈中的位置,下图是IPV4中的检查
  225. 点:
  226. [IMG]http://www.nsfocus.net/magazine_upload/42_002.jpg[/IMG]
  227.                 图2 IPV4中的检查点


  228. 图中检查点的名称如下:

  229. 检查点编号    检查点名称           检查点所在文件名
  230. 1             NF_IP_PRE_ROUTING    ip_input.c
  231. 2             NF_IP_LOCAL_IN       ip_input.c
  232. 3             NF_IP_FORWARD        ip_forward.c
  233. 4             NF_IP_POST_ROUTING   ip_output.c
  234. 5             NF_IP_LOCAL_OUT      ip_output.c

  235. 图中,ROUTE(1)处对收到的包做路由查找并判断这个包是需要转发的包还是发往
  236. 本机上层的包,ROUTE(2)处查找发出包的路由。NF_IP_PRE_ROUTING处对所有传
  237. 入IP层的数据包进行检查,在这之前,有关数据包的版本、长度、校验和等正确
  238. 性检查已经完成。NF_IP_LOCAL_IN对发往本机上层的数据包进行检查。
  239. NF_IP_FORWARD处检查需要转发的数据包。NF_IP_POST_ROUTING处对所有向链路
  240. 层传递的数据包进行检查,注意在此处数据包的路由已经确定。
  241. NF_IP_LOCAL_OUT对本机发出的包进行检查,此处的路由还没有确定,所以可以
  242. 做目的地址转换。实现某个网络安全功能可能需要在多个检查点上注册相应的结
  243. 构,在后面的分析中我们可以看到具体的例子。

  244. 检查点的作用已经明确,那么下面就来看每个检查点的具体位置。正如上表中显
  245. 示的一样,各个检查点分别在上面列出的文件中被HOOK。
  246. 首先看NF_IP_PRE_ROUTING检查点,它在ip_input.c中出现的位置只有一处:
  247. int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt) 函数中:

  248. 376 /*
  249. 377  *      Main IP Receive routine。
  250. 378  */
  251. 379 int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt)
  252. 380 {
  253. 381         struct iphdr *iph;
  254. 382
  255. 383        /* When the interface is in promisc。 mode, drop all the crap
  256. 384          * that it receives, do not try to analyse it。
  257. 385          */
  258. 386         if (skb->pkt_type == PACKET_OTHERHOST)
  259. 387                 goto drop;
  260. 388
  261. 389         IP_INC_STATS_BH(IpInReceives);
  262. 390
  263. 391         if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL)
  264. 392                 goto out;
  265. 393
  266. 394         if (!pskb_may_pull(skb, sizeof(struct iphdr)))
  267. 395                 goto inhdr_error;
  268. 396
  269. 397         iph = skb->nh。iph;
  270. 398
  271. 399         /*
  272. 400          * RFC1122: 3。1。2。2 MUST silently discard any IP frame that
  273. ?        fails the checksum。
  274. 401          *
  275. 402          *      Is the datagram acceptable?
  276. 403          *
  277. 404          *      1。      Length at least the size of an ip header
  278. 405          *      2。      Version of 4
  279. 406          *      3。      Checksums correctly。 [Speed optimisation
  280. ?        for later, skip loopback checksums]
  281. 407          *      4。      Doesn’t have a bogus length
  282. 408          */
  283. 409
  284. 410         if (iph->ihl < 5 || iph->version != 4)
  285. 411                 goto inhdr_error;
  286. 412
  287. 413         if (!pskb_may_pull(skb, iph->ihl*4))
  288. 414                 goto inhdr_error;
  289. 415
  290. 416         iph = skb->nh。iph;
  291. 417
  292. 418         if (ip_fast_csum((u8 *)iph, iph->ihl) != 0)
  293. 419                 goto inhdr_error;
  294. 420
  295. 421         {
  296. 422                 __u32 len = ntohs(iph->tot_len);
  297. 423                 if (skb->len < len || len < (iph->ihl<<2))
  298. 424                         goto inhdr_error;
  299. 425
  300. 426     /*Our transport medium may have padded the buffer out.Now we
  301. 427               * know it is IP we can trim to the true length of the frame。
  302. 428               * Note this now means skb->len holds ntohs(iph->tot_len)。
  303. 429       */
  304. 430                 if (skb->len > len) {
  305. 431                         __pskb_trim(skb, len);
  306. 432                         if (skb->ip_summed == CHECKSUM_HW)
  307. 433                                 skb->ip_summed = CHECKSUM_NONE;
  308. 434                 }
  309. 435         }
  310. 436
  311. 437         return NF_HOOK(PF_INET, NF_IP_PRE_ROUTING, skb, dev, NULL,
  312. 438                        ip_rcv_finish);
  313. 439
  314. 440 inhdr_error:
  315. 441         IP_INC_STATS_BH(IpInHdrErrors);
  316. 442 drop:
  317. 443         kfree_skb(skb);
  318. 444 out:
  319. 445         return NET_RX_DROP;
  320. 446 }

  321. 在437行中我们看到这里调用了前面说明的NF_HOOK宏, 传给它的参数分别是:
  322. PF_INET:IPV4协议栈
  323. NF_IP_PRE_ROUTING: 刚刚讨论的检查点。
  324. skb: sk_buff结构
  325. dev: 输入接口
  326. ip_rcv_finish: okfn指向的后续处理函数。
  327. 在调用NF_HOOK之前我们还看到了在这个函数里对这个skb做了一些基本的检查:
  328. 如版本,长度,校验和等等。另外的4各HOOK点也在相应的文件里做了相同的处理
  329. ,但有一点区别的是,可能一些点上只有输入接口,如: NF_IP_PRE_ROUTING,而有
  330. 些只有输出接口,如: NF_IP_POST_ROUTING ,还可能输入输出的接口都有,如:
  331. NF_IP_FORWARD。

  332. 总结
  333. 从上面的分析中我们可以看出,一个数据包在经过IPV4这个协议栈做处理时,原则
  334. 上其流程是不会因为加入Netfilter框架而改变,Netfilter所要做的事情只不过
  335. 是在一些必要的地方看一下这些数据包,并根据用户的意愿对这个数据包处理。
  336. 为了更清晰的显示Netfilter框架中的几个HOOK点在IPV4中的位置,可以从下图
  337. 中清晰的反映出来。图中椭圆中为处理函数,用箭头指出的函数中的点为调用宏
  338. NF_HOOK处,箭头以为数据包的流程方向。(此处为突出重点,只画出了IP层的大部分和Netfilter密切相关的一些函数)

  339. 图3 各个HOOK 点

  340. 由于不知道如何引用上传的图片,:( 此图在附件中!

复制代码

以上的某些图片是引用了别人的成果,在这里表示感谢!
此中错误在所难免,望大家能多多交流!!!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x
发表于 2004-6-22 09:38:50 | 显示全部楼层
顶。
发表于 2004-6-25 23:53:32 | 显示全部楼层
顶了!这个角度讲的好!
发表于 2004-6-26 06:21:27 | 显示全部楼层
请问楼主,上面的图片是从哪里获得的?
发表于 2004-6-26 09:47:38 | 显示全部楼层
没时间学c,看不懂啊!可惜啊。。。。。

支持ing
发表于 2004-6-27 00:52:43 | 显示全部楼层
Good job。
 楼主| 发表于 2004-6-27 14:12:10 | 显示全部楼层
你说那些图片,引用的是别人的图片,
附件中的是我做了修改的图片!
发表于 2004-6-29 03:51:21 | 显示全部楼层
谢谢分享!
发表于 2004-7-15 15:10:48 | 显示全部楼层
不错不错
谢谢
 楼主| 发表于 2004-7-17 20:36:42 | 显示全部楼层

tks

谢谢兄弟们的支持呀,不过总觉得这个版的人气太弱,都发了这么就的帖子了,尽然可以在第一页还能看见.
许久没过来了,一直在为生活奔波!
您需要登录后才可以回帖 登录 | 注册

本版积分规则

快速回复 返回顶部 返回列表