2004年12月15日

这个测试是菲尔博士在著名女黑人欧普拉的节目里做的,满准确的。答覆是依现在的
您,不要依过去的您。这是一个目前很多大公司人事部门实际采用的测试
  
1.你何时感觉最好?
a)早晨
b)下午及傍晚
c)夜里

2.你走路时是……
a)大步的快走
b)小步的快走
c)不快,仰著头面对著世界
d)不快,低著头
e)很慢

3.和人说话时,你……
a)手臂交叠的站著
b)双手紧握著
c)一只手或两手放在臀部
d)碰著或推著与你说话的人
e)玩著你的耳朵、摸著你的下巴、或用手整理头发

4.坐著休息时,你的……
a)两膝盖并拢
b)两腿交叉
c)两腿伸直
d)一腿卷在身下

5.碰到你感到发笑的事时,你的反应是……
a)一个欣赏的大笑
b)笑著,但不大声
c)轻声的咯咯地笑
d)羞怯的微笑

6.当你去一个派对或社交场合时,你……
a)很大声地入场以引起注意
b)安静地入场,找你认识的人
c)非常安静地入场,尽量保持不被注意

7.当你非常专心工作时,有人打断你,你会……
a)欢迎他
b)感到非常恼怒
c)在上两极端之间

8.下列颜色中,你最喜欢哪一颜色?
a)红或橘色
b)黑色
c)黄或浅蓝色
d)绿色
e)深蓝或紫色
f)白色
g)棕或灰色

9.临入睡的前几分钟,你在床上的姿势是……
a)仰躺,伸直
b)俯躺,伸直
c)侧躺,微卷
d)头睡在一手臂上
e)被盖过头

10.你经常梦到你在……
a)落下
b)打架或挣扎
c)找东西或人
d)飞或漂浮
e)你平常不做梦
f)你的梦都是愉快的


现在将所有分数相加,再对照后面的分析
分 数
1.(a) 2 (b) 4 (c) 6
2.(a) 6 (b) 4 (c) 7 (d) 2 (e) 1
3.(a) 4 (b) 2 (c) 5 (d) 7 (e) 6
4.(a) 4 (b) 6 (c) 2 (d) 1
5.(a) 6 (b) 4 (c) 3 (d) 5
6.(a) 6 (b) 4 (c) 2
7.(a) 6 (b) 2 (c) 4
8.(a) 6 (b) 7 (c) 5 (d) 4 (e) 3 (f) 2 (g) 1
9.(a) 7 (b) 6 (c) 4 (d) 2 (e) 1
10.(a) 4 (b) 2 (c) 3 (d) 5 (e) 6 (f) 1

【低於21分:内向的悲观者】
人们认为你是一个害羞的、神经质的、优柔寡断的,是须人照顾、永远要别人为你
做决定、不想与任何事或任何人有关。他们认为你是一个杞人忧天者,一个永远看
到不存在的问题的人。有些人认为你令人乏味,只有那些深知你的人知道你不是这
样的人。
【21分到30分:缺乏信心的挑剔者】
你的朋友认为你勤勉刻苦、很挑剔。他们认为你是一个谨慎的、十分小心的
人,一个缓慢而稳定辛勤工作的人。如果你做任何冲动的事或无准备的事,你会令
他们大吃一惊。他们认为你会从各个角度仔细地检查一切之后仍经常决定不做。
他们认为对你的这种反应一部分是因为你的小心的天性所引起的。

【31分到40分:以牙还牙的自我保护者】
别人认为你是一个明智、谨慎、注重实效的人。也认为你是一个伶俐、有天赋有才
干且谦虚的人。你不会很快、很容易和人成为朋友,但是是一个对朋友非常忠诚的
人,同时要求朋友对你也有忠诚的回报。那些真正有机会了解愕娜嘶嶂酪?
你对朋友的信任是很难的,但相等的,一旦这信任被破坏,会使你很难熬过。

【41分到50分:平衡的中道】
别人认为你是一个新鲜的、有活力的、有魅力的、好玩的、讲究实际的、而永远有
趣的人;一个经常是群众注意力的焦点,但是你是一个足够平衡的人,不至於因此
而昏了头。他们也认为你亲切、和蔼、体贴、能谅解人;一个永远会使人高兴起来
并会帮助别人的人。

【51分到60分:吸引人的冒险家】
别人认为你是一个令人兴奋的、高度活泼的、相当易冲动的个性;你是一个天生的领
袖、一个做决定会很快的人,虽然你的决定不总是对的。 他们认为你是大胆的
和冒
险的,会愿意试做任何事至少一次;是一个愿意尝试机会而欣赏冒险的人。因
为你散
发的刺激,他们喜欢跟你在一起。

【60分以上:傲慢的孤独者】
别人认为对你必须「小心处理」。在别人的眼中,你是自负的、自我中心的、
是个极端有支配欲、统治欲的。别人可能钦佩你,希望能多像你一点,但不会永远
相信你,会对与你更深入的来往有所踌躇及犹豫.世界本来就是层层嵌套,周而复
始;不以任何的意志而改变。

2004年12月10日

不和父亲顶嘴
  
  会哄妈妈开心
  
  不轻易喝醉
  
  不轻易对人说要戒烟,反反复复只会让人觉得言而无信
  
  和她在一起无话可说时,懂得伸手去揽她的肩
  
  失恋的雨夜,自己回家品红酒听肖邦,而不是去站在她楼下淋雨折磨她
  
  不怕孤单
  
  情人节和生日记得给前女友祝贺,仅此而已
  
  女生倒在你怀里哭诉衷肠后,第二天一切正常,不为所动
  
  不轻易发誓,不轻易相信誓言
  
  忘记她的誓言
  
  不轻易愤怒
  
  学会宽恕,分享
  
  不对女生说下流笑话
  
  会送恐龙回家
  
  和美女ONS后,不再纠缠她,拿的起放的下
  
  接受比自己小但有才华的人超过自己
  
  有2年期的人生目标

2004年12月02日

在 Linux 上找出并解决程序错误的主要方法

Steve Bestsbest@us.ibm.com
JFS 核心小组成员,IBM
2002 年 8 月


您可以用各种方法来监控运行着的用户空间程序:可以为其运行调试器并单步调试该程序,添加打印语句,或者添加工具来分析程序。本文描述了几种可以用来调试在 Linux 上运行的程序的方法。我们将回顾四种调试问题的情况,这些问题包括段错误,内存溢出和泄漏,还有挂起。

本文讨论了四种调试 Linux 程序的情况。在第 1 种情况中,我们使用了两个有内存分配问题的样本程序,使用 MEMWATCH 和 Yet Another Malloc Debugger(YAMD)工具来调试它们。在第 2 种情况中,我们使用了 Linux 中的 strace 实用程序,它能够跟踪系统调用和信号,从而找出程序发生错误的地方。在第 3 种情况中,我们使用 Linux 内核的 Oops 功能来解决程序的段错误,并向您展示如何设置内核源代码级调试器(kernel source level debugger,kgdb),以使用 GNU 调试器(GNU debugger,gdb)来解决相同的问题;kgdb 程序是使用串行连接的 Linux 内核远程 gdb。在第 4 种情况中,我们使用 Linux 上提供的魔术键控顺序(magic key sequence)来显示引发挂起问题的组件的信息。


常见调试方法
当您的程序中包含错误时,很可能在代码中某处有一个条件,您认为它为真(true),但实际上是假(false)。找出错误的过程也就是在找出错误后推翻以前一直确信为真的某个条件过程。


以下几个示例是您可能确信成立的条件的一些类型:


  • 在源代码中的某处,某变量有特定的值。
  • 在给定的地方,某个结构已被正确设置。
  • 对于给定的 if-then-else 语句,if 部分就是被执行的路径。
  • 当子例程被调用时,该例程正确地接收到了它的参数。


找出错误也就是要确定上述所有情况是否存在。如果您确信在子例程被调用时某变量应该有特定的值,那么就检查一下情况是否如此。如果您相信 if 结构会被执行,那么也检查一下情况是否如此。通常,您的假设都会是正确的,但最终您会找到与假设不符的情况。结果,您就会找出发生错误的地方。


调试是您无法逃避的任务。进行调试有很多种方法,比如将消息打印到屏幕上、使用调试器,或只是考虑程序执行的情况并仔细地揣摩问题所在。


在修正问题之前,您必须找出它的源头。举例来说,对于段错误,您需要了解段错误发生在代码的哪一行。一旦您发现了代码中出错的行,请确定该方法中变量的值、方法被调用的方式以及关于错误如何发生的详细情况。使用调试器将使找出所有这些信息变得很简单。如果没有调试器可用,您还可以使用其它的工具。(请注意,产品环境中可能并不提供调试器,而且 Linux 内核没有内建的调试器。)





实用的内存和内核工具
您可以使用 Linux 上的调试工具,通过各种方式跟踪用户空间和内核问题。请使用下面的工具和技术来构建和调试您的源代码:

用户空间工具


  • 内存工具:MEMWATCH 和 YAMD
  • strace
  • GNU 调试器(gdb)
  • 魔术键控顺序


内核工具


  • 内核源代码级调试器(kgdb)
  • 内建内核调试器(kdb)
  • Oops


本文将讨论一类通过人工检查代码不容易找到的问题,而且此类问题只在很少见的情况下存在。内存错误通常在多种情况同时存在时出现,而且您有时只能在部署程序之后才能发现内存错误。


第 1 种情况:内存调试工具
C 语言作为 Linux 系统上标准的编程语言给予了我们对动态内存分配很大的控制权。然而,这种自由可能会导致严重的内存管理问题,而这些问题可能导致程序崩溃或随时间的推移导致性能降级。


内存泄漏(即 malloc() 内存在对应的 free() 调用执行后永不被释放)和缓冲区溢出(例如对以前分配到某数组的内存进行写操作)是一些常见的问题,它们可能很难检测到。这一部分将讨论几个调试工具,它们极大地简化了检测和找出内存问题的过程。


MEMWATCH
MEMWATCH 由 Johan Lindh 编写,是一个开放源代码 C 语言内存错误检测工具,您可以自己下载它(请参阅本文后面部分的参考资料)。只要在代码中添加一个头文件并在 gcc 语句中定义了 MEMWATCH 之后,您就可以跟踪程序中的内存泄漏和错误了。MEMWATCH 支持 ANSI C,它提供结果日志纪录,能检测双重释放(double-free)、错误释放(erroneous free)、没有释放的内存(unfreed memory)、溢出和下溢等等。

清单 1. 内存样本(test1.c)




#include <stdlib.h>
#include <stdio.h>
#include “memwatch.h”

int main(void)
{
char *ptr1;
char *ptr2;

ptr1 = malloc(512);
ptr2 = malloc(512);

ptr2 = ptr1;
free(ptr2);
free(ptr1);
}


清单 1 中的代码将分配两个 512 字节的内存块,然后指向第一个内存块的指针被设定为指向第二个内存块。结果,第二个内存块的地址丢失,从而产生了内存泄漏。


现在我们编译清单 1 的 memwatch.c。下面是一个 makefile 示例:

test1




gcc -DMEMWATCH -DMW_STDIO test1.c memwatch
c -o test1

当您运行 test1 程序后,它会生成一个关于泄漏的内存的报告。清单 2 展示了示例 memwatch.log 输出文件。

清单 2. test1 memwatch.log 文件




MEMWATCH 2.67 Copyright (C) 1992-1999 Johan Lindh


double-free: <4> test1.c(15), 0×80517b4 was freed from test1.c(14)

unfreed: <2> test1.c(11), 512 bytes at 0×80519e4
{FE FE FE FE FE FE FE FE FE FE FE FE …………..}

Memory usage statistics (global):
N)umber of allocations made: 2
L)argest memory usage : 1024
T)otal of all alloc() calls: 1024
U)nfreed bytes totals : 512


MEMWATCH 为您显示真正导致问题的行。如果您释放一个已经释放过的指针,它会告诉您。对于没有释放的内存也一样。日志结尾部分显示统计信息,包括泄漏了多少内存,使用了多少内存,以及总共分配了多少内存。


YAMD
YAMD 软件包由 Nate Eldredge 编写,可以查找 C 和 C++ 中动态的、与内存分配有关的问题。在撰写本文时,YAMD 的最新版本为 0.32。请下载 yamd-0.32.tar.gz(请参阅参考资料)。执行 make 命令来构建程序;然后执行 make install 命令安装程序并设置工具。


一旦您下载了 YAMD 之后,请在 test1.c 上使用它。请删除 #include memwatch.h 并对 makefile 进行如下小小的修改:

使用 YAMD 的 test1




gcc -g test1.c -o test1

清单 3 展示了来自 test1 上的 YAMD 的输出。

清单 3. 使用 YAMD 的 test1 输出




YAMD version 0.32
Executable: /usr/src/test/yamd-0.32/test1

INFO: Normal allocation of this block
Address 0×40025e00, size 512

INFO: Normal allocation of this block
Address 0×40028e00, size 512

INFO: Normal deallocation of this block
Address 0×40025e00, size 512

ERROR: Multiple freeing At
free of pointer already freed
Address 0×40025e00, size 512

WARNING: Memory leak
Address 0×40028e00, size 512
WARNING: Total memory leaks:
1 unfreed allocations totaling 512 bytes

*** Finished at Tue … 10:07:15 2002
Allocated a grand total of 1024 bytes 2 allocations
Average of 512 bytes per allocation
Max bytes allocated at one time: 1024
24 K alloced internally / 12 K mapped now / 8 K max
Virtual program size is 1416 K
End.


YAMD 显示我们已经释放了内存,而且存在内存泄漏。让我们在清单 4 中另一个样本程序上试试 YAMD。

清单 4. 内存代码(test2.c)




#include <stdlib.h>
#include <stdio.h>

int main(void)
{
char *ptr1;
char *ptr2;
char *chptr;
int i = 1;
ptr1 = malloc(512);
ptr2 = malloc(512);
chptr = (char *)malloc(512);
for (i; i <= 512; i++) {
chptr[i] = ‘S’;
}
ptr2 = ptr1;
free(ptr2);
free(ptr1);
free(chptr);
}


您可以使用下面的命令来启动 YAMD:


./run-yamd /usr/src/test/test2/test2

清单 5 显示了在样本程序 test2 上使用 YAMD 得到的输出。YAMD 告诉我们在 for 循环中有“越界(out-of-bounds)”的情况。

清单 5. 使用 YAMD 的 test2 输出




Running /usr/src/test/test2/test2
Temp output to /tmp/yamd-out.1243
*********
./run-yamd: line 101: 1248 Segmentation fault (core dumped)
YAMD version 0.32
Starting run: /usr/src/test/test2/test2
Executable: /usr/src/test/test2/test2
Virtual program size is 1380 K

INFO: Normal allocation of this block
Address 0×40025e00, size 512

INFO: Normal allocation of this block
Address 0×40028e00, size 512

INFO: Normal allocation of this block
Address 0×4002be00, size 512
ERROR: Crash

Tried to write address 0×4002c000
Seems to be part of this block:
Address 0×4002be00, size 512

Address in question is at offset 512 (out of bounds)
Will dump core after checking heap.
Done.

MEMWATCH 和 YAMD 都是很有用的调试工具,它们的使用方法有所不同。对于 MEMWATCH,您需要添加包含文件 memwatch.h 并打开两个编译时间标记。对于链接(link)语句,YAMD 只需要 -g 选项。


Electric Fence
多数 Linux 分发版包含一个 Electric Fence 包,不过您也可以选择下载它。Electric Fence 是一个由 Bruce Perens 编写的 malloc() 调试库。它就在您分配内存后分配受保护的内存。如果存在 fencepost 错误(超过数组末尾运行),程序就会产生保护错误,并立即结束。通过结合 Electric Fence 和 gdb,您可以精确地跟踪到哪一行试图访问受保护内存。Electric Fence 的另一个功能就是能够检测内存泄漏。


第 2 种情况:使用 strace
strace 命令是一种强大的工具,它能够显示所有由用户空间程序发出的系统调用。strace 显示这些调用的参数并返回符号形式的值。strace 从内核接收信息,而且不需要以任何特殊的方式来构建内核。将跟踪信息发送到应用程序及内核开发者都很有用。在清单 6 中,分区的一种格式有错误,清单显示了 strace 的开头部分,内容是关于调出创建文件系统操作(mkfs)的。strace 确定哪个调用导致问题出现。

清单 6. mkfs 上 strace 的开头部分




execve(“/sbin/mkfs.jfs”, ["mkfs.jfs", "-f", "/dev/test1"], &

open(“/dev/test1″, O_RDWR|O_LARGEFILE) = 4
stat64(“/dev/test1″, {st_mode=&, st_rdev=makedev(63, 255), …}) = 0
ioctl(4, 0×40041271, 0xbfffe128) = -1 EINVAL (Invalid argument)
write(2, “mkfs.jfs: warning – cannot setb” …, 98mkfs.jfs: warning -
cannot set blocksize on block device /dev/test1: Invalid argument )
= 98
stat64(“/dev/test1″, {st_mode=&, st_rdev=makedev(63, 255), …}) = 0
open(“/dev/test1″, O_RDONLY|O_LARGEFILE) = 5
ioctl(5, 0×80041272, 0xbfffe124) = -1 EINVAL (Invalid argument)
write(2, “mkfs.jfs: can\’t determine device”…, …_exit(1)
= ?

清单 6 显示 ioctl 调用导致用来格式化分区的 mkfs 程序失败。ioctl BLKGETSIZE64 失败。(BLKGET-SIZE64 在调用 ioctl 的源代码中定义。) BLKGETSIZE64 ioctl 将被添加到 Linux 中所有的设备,而在这里,逻辑卷管理器还不支持它。因此,如果 BLKGETSIZE64 ioctl 调用失败,mkfs 代码将改为调用较早的 ioctl 调用;这使得 mkfs 适用于逻辑卷管理器。


第 3 种情况:使用 gdb 和 Oops
您可以从命令行使用 gdb 程序(Free Software Foundation 的调试器)来找出错误,也可以从诸如 Data Display Debugger(DDD)这样的几个图形工具之一使用 gdb 程序来找出错误。您可以使用 gdb 来调试用户空间程序或 Linux 内核。这一部分只讨论从命令行运行 gdb 的情况。


使用 gdb program name 命令启动 gdb。gdb 将载入可执行程序符号并显示输入提示符,让您可以开始使用调试器。您可以通过三种方式用 gdb 查看进程:



  • 使用 attach 命令开始查看一个已经运行的进程;attach 将停止进程。


  • 使用 run 命令执行程序并从头开始调试程序。


  • 查看已有的核心文件来确定进程终止时的状态。要查看核心文件,请用下面的命令启动 gdb。
    gdb programname corefilename

    要用核心文件进行调试,您不仅需要程序的可执行文件和源文件,还需要核心文件本身。要用核心文件启动 gdb,请使用 -c 选项:

    gdb -c core programname


    gdb 显示哪行代码导致程序发生核心转储。


在运行程序或连接到已经运行的程序之前,请列出您觉得有错误的源代码,设置断点,然后开始调试程序。您可以使用 help 命令查看全面的 gdb 在线帮助和详细的教程。


kgdb
kgdb 程序(使用 gdb 的远程主机 Linux 内核调试器)提供了一种使用 gdb 调试 Linux 内核的机制。kgdb 程序是内核的扩展,它让您能够在远程主机上运行 gdb 时连接到运行用 kgdb 扩展的内核机器。您可以接着深入到内核中、设置断点、检查数据并进行其它操作(类似于您在应用程序上使用 gdb 的方式)。这个补丁的主要特点之一就是运行 gdb 的主机在引导过程中连接到目标机器(运行要被调试的内核)。这让您能够尽早开始调试。请注意,补丁为 Linux 内核添加了功能,所以 gdb 可以用来调试 Linux 内核。


使用 kgdb 需要两台机器:一台是开发机器,另一台是测试机器。一条串行线(空调制解调器电缆)将通过机器的串口连接它们。您希望调试的内核在测试机器上运行;gdb 在开发机器上运行。gdb 使用串行线与您要调试的内核通信。


请遵循下面的步骤来设置 kgdb 调试环境:




  1. 下载您的 Linux 内核版本适用的补丁。


  2. 将组件构建到内核,因为这是使用 kgdb 最简单的方法。(请注意,有两种方法可以构建多数内核组件,比如作为模块或直接构建到内核中。举例来说,日志纪录文件系统(Journaled File System,JFS)可以作为模块构建,或直接构建到内核中。通过使用 gdb 补丁,我们就可以将 JFS 直接构建到内核中。)


  3. 应用内核补丁并重新构建内核。


  4. 创建一个名为 .gdbinit 的文件,并将其保存在内核源文件子目录中(换句话说就是 /usr/src/linux)。文件 .gdbinit 中有下面四行代码:

    • set remotebaud 115200
    • symbol-file vmlinux
    • target remote /dev/ttyS0
    • set output-radix 16


  5. 将 append=gdb 这一行添加到 lilo,lilo 是用来在引导内核时选择使用哪个内核的引导载入程序。

    • image=/boot/bzImage-2.4.17
    • label=gdb2417
    • read-only
    • root=/dev/sda8
    • append=”gdb gdbttyS=1 gdb-baud=115200 nmi_watchdog=0″


清单 7 是一个脚本示例,它将您在开发机器上构建的内核和模块引入测试机器。您需要修改下面几项:




  • best@sfb:用户标识和机器名。
  • /usr/src/linux-2.4.17:内核源代码树的目录。
  • bzImage-2.4.17:测试机器上将引导的内核名。
  • rcprsync:必须允许它在构建内核的机器上运行。

清单 7. 引入测试机器的内核和模块的脚本




set -x
rcp best@sfb: /usr/src/linux-2.4.17/arch/i386/boot/bzImage /boot/bzImage-2.4.17
rcp best@sfb:/usr/src/linux-2.4.17/System.map /boot/System.map-2.4.17
rm -rf /lib/modules/2.4.17
rsync -a best@sfb:/lib/modules/2.4.17 /lib/modules
chown -R root /lib/modules/2.4.17
lilo

现在我们可以通过改为使用内核源代码树开始的目录来启动开发机器上的 gdb 程序了。在本示例中,内核源代码树位于 /usr/src/linux-2.4.17。输入 gdb 启动程序。


如果一切正常,测试机器将在启动过程中停止。输入 gdb 命令 cont 以继续启动过程。一个常见的问题是,空调制解调器电缆可能会被连接到错误的串口。如果 gdb 不启动,将端口改为第二个串口,这会使 gdb 启动。


使用 kgdb 调试内核问题
清单 8 列出了 jfs_mount.c 文件的源代码中被修改过的代码,我们在代码中创建了一个空指针异常,从而使代码在第 109 行产生段错误。

清单 8. 修改过后的 jfs_mount.c 代码




int jfs_mount(struct super_block *sb)
{

int ptr; /* line 1 added */
jFYI(1, (“\nMount JFS\n”));
/ *
* read/validate superblock
* (initialize mount inode from the superblock)
* /
if ((rc = chkSuper(sb))) {
goto errout20;
}
108 ptr=0; /* line 2 added */
109 printk(“%d\n”,*ptr); /* line 3 added */

清单 9 在向文件系统发出 mount 命令之后显示一个 gdb 异常。kgdb 提供了几条命令,如显示数据结构和变量值以及显示系统中的所有任务处于什么状态、它们驻留在何处、它们在哪些地方使用了 CPU 等等。清单 9 将显示回溯跟踪为该问题提供的信息;where 命令用来执行反跟踪,它将告诉被执行的调用在代码中的什么地方停止。

清单 9. gdb 异常和反跟踪




mount -t jfs /dev/sdb /jfs

Program received signal SIGSEGV, Segmentation fault.
jfs_mount (sb=0xf78a3800) at jfs_mount.c:109
109 printk(“%d\n”,*ptr);
(gdb)where
#0 jfs_mount (sb=0xf78a3800) at jfs_mount.c:109
#1 0xc01a0dbb in jfs_read_super … at super.c:280
#2 0xc0149ff5 in get_sb_bdev … at super.c:620
#3 0xc014a89f in do_kern_mount … at super.c:849
#4 0xc0160e66 in do_add_mount … at namespace.c:569
#5 0xc01610f4 in do_mount … at namespace.c:683
#6 0xc01611ea in sys_mount … at namespace.c:716
#7 0xc01074a7 in system_call () at af_packet.c:1891
#8 0×0 in ?? ()
(gdb)


下一部分还将讨论这个相同的 JFS 段错误问题,但不设置调试器,如果您在非 kgdb 内核环境中执行清单 8 中的代码,那么它使用内核可能生成的 Oops 消息。


Oops 分析
Oops(也称 panic,慌张)消息包含系统错误的细节,如 CPU 寄存器的内容。在 Linux 中,调试系统崩溃的传统方法是分析在发生崩溃时发送到系统控制台的 Oops 消息。一旦您掌握了细节,就可以将消息发送到 ksymoops 实用程序,它将试图将代码转换为指令并将堆栈值映射到内核符号。在很多情况下,这些信息就足够您确定错误的可能原因是什么了。请注意,Oops 消息并不包括核心文件。


让我们假设系统刚刚创建了一条 Oops 消息。作为编写代码的人,您希望解决问题并确定什么导致了 Oops 消息的产生,或者您希望向显示了 Oops 消息的代码的开发者提供有关您的问题的大部分信息,从而及时地解决问题。Oops 消息是等式的一部分,但如果不通过 ksymoops 程序运行它也于事无补。下面的图显示了格式化 Oops 消息的过程。


格式化 Oops 消息
格式化 Oops 消息


ksymoops 需要几项内容:Oops 消息输出、来自正在运行的内核的 System.map 文件,还有 /proc/ksyms、vmlinux 和 /proc/modules。关于如何使用 ksymoops,内核源代码 /usr/src/linux/Documentation/oops-tracing.txt 中或 ksymoops 手册页上有完整的说明可以参考。Ksymoops 反汇编代码部分,指出发生错误的指令,并显示一个跟踪部分表明代码如何被调用。


首先,将 Oops 消息保存在一个文件中以便通过 ksymoops 实用程序运行它。清单 10 显示了由安装 JFS 文件系统的 mount 命令创建的 Oops 消息,问题是由清单 8 中添加到 JFS 安装代码的那三行代码产生的。

清单 10. ksymoops 处理后的 Oops 消息




ksymoops 2.4.0 on i686 2.4.17. Options used
… 15:59:37 sfb1 kernel: Unable to handle kernel NULL pointer dereference at
virtual address 0000000
… 15:59:37 sfb1 kernel: c01588fc
… 15:59:37 sfb1 kernel: *pde = 0000000
… 15:59:37 sfb1 kernel: Oops: 0000
… 15:59:37 sfb1 kernel: CPU: 0
… 15:59:37 sfb1 kernel: EIP: 0010:[jfs_mount+60/704]

… 15:59:37 sfb1 kernel: Call Trace: [jfs_read_super+287/688]
[get_sb_bdev+563/736] [do_kern_mount+189/336] [do_add_mount+35/208]
[do_page_fault+0/1264]
… 15:59:37 sfb1 kernel: Call Trace: [<c0155d4f>]…
… 15:59:37 sfb1 kernel: [<c0106e04 …
… 15:59:37 sfb1 kernel: Code: 8b 2d 00 00 00 00 55 …

>>EIP; c01588fc <jfs_mount+3c/2c0> <=====

Trace; c0106cf3 <system_call+33/40>
Code; c01588fc <jfs_mount+3c/2c0>
00000000 <_EIP>:
Code; c01588fc <jfs_mount+3c/2c0> <=====
0: 8b 2d 00 00 00 00 mov 0×0,%ebp <=====
Code; c0158902 <jfs_mount+42/2c0>
6: 55 push %ebp


接下来,您要确定 jfs_mount 中的哪一行代码引起了这个问题。Oops 消息告诉我们问题是由位于偏移地址 3c 的指令引起的。做这件事的办法之一是对 jfs_mount.o 文件使用 objdump 实用程序,然后查看偏移地址 3c。Objdump 用来反汇编模块函数,看看您的 C 源代码会产生什么汇编指令。清单 11 显示了使用 objdump 后您将看到的内容,接着,我们查看 jfs_mount 的 C 代码,可以看到空值是第 109 行引起的。偏移地址 3c 之所以很重要,是因为 Oops 消息将该处标识为引起问题的位置。

清单 11. jfs_mount 的汇编程序清单




109 printk(“%d\n”,*ptr);

objdump jfs_mount.o

jfs_mount.o: file format elf32-i386

Disassembly of section .text:

00000000 <jfs_mount>:
0:55 push %ebp

2c: e8 cf 03 00 00 call 400 <chkSuper>
31: 89 c3 mov %eax,%ebx
33: 58 pop %eax
34: 85 db test %ebx,%ebx
36: 0f 85 55 02 00 00 jne 291 <jfs_mount+0×291>
3c: 8b 2d 00 00 00 00 mov 0×0,%ebp << problem line above
42: 55 push %ebp


kdb
Linux 内核调试器(Linux kernel debugger,kdb)是 Linux 内核的补丁,它提供了一种在系统能运行时对内核内存和数据结构进行检查的办法。请注意,kdb 不需要两台机器,不过它也不允许您像 kgdb 那样进行源代码级别上的调试。您可以添加额外的命令,给出该数据结构的标识或地址,这些命令便可以格式化和显示基本的系统数据结构。目前的命令集允许您控制包括以下操作在内的内核操作:




  • 处理器单步执行
  • 执行到某条特定指令时停止
  • 当存取(或修改)某个特定的虚拟内存位置时停止
  • 当存取输入/输出地址空间中的寄存器时停止
  • 对当前活动的任务和所有其它任务进行堆栈回溯跟踪(通过进程 ID)
  • 对指令进行反汇编





追击内存溢出

您肯定不想陷入类似在几千次调用之后发生分配溢出这样的情形。


我们的小组花了许许多多时间来跟踪稀奇古怪的内存错误问题。应用程序在我们的开发工作站上能运行,但在新的产品工作站上,这个应用程序在调用 malloc() 两百万次之后就不能运行了。真正的问题是在大约一百万次调用之后发生了溢出。新系统之所有存在这个问题,是因为被保留的 malloc() 区域的布局有所不同,从而这些零散内存被放置在了不同的地方,在发生溢出时破坏了一些不同的内容。


我们用多种不同技术来解决这个问题,其中一种是使用调试器,另一种是在源代码中添加跟踪功能。在我职业生涯的大概也是这个时候,我便开始关注内存调试工具,希望能更快更有效地解决这些类型的问题。在开始一个新项目时,我最先做的事情之一就是运行 MEMWATCH 和 YAMD,看看它们是不是会指出内存管理方面的问题。


内存泄漏是应用程序中常见的问题,不过您可以使用本文所讲述的工具来解决这些问题。


第 4 种情况:使用魔术键控顺序进行回溯跟踪
如果在 Linux 挂起时您的键盘仍然能用,那请您使用以下方法来帮助解决挂起问题的根源。遵循这些步骤,您便可以显示当前运行的进程和所有使用魔术键控顺序的进程的回溯跟踪。




  1. 您正在运行的内核必须是在启用 CONFIG_MAGIC_SYS-REQ 的情况下构建的。您还必须处在文本模式。CLTR+ALT+F1 会使您进入文本模式,CLTR+ALT+F7 会使您回到 X Windows。
  2. 当在文本模式时,请按 <ALT+ScrollLock>,然后按 <Ctrl+ScrollLock>。上述魔术的击键会分别给出当前运行的进程和所有进程的堆栈跟踪。
  3. 请查找 /var/log/messages。如果一切设置正确,则系统应该已经为您转换了内核的符号地址。回溯跟踪将被写到 /var/log/messages 文件中。


结束语
帮助调试 Linux 上的程序有许多不同的工具可供使用。本文讲述的工具可以帮助您解决许多编码问题。能显示内存泄漏、溢出等等的位置的工具可以解决内存管理问题,我发现 MEMWATCH 和 YAMD 很有帮助。


使用 Linux 内核补丁会使 gdb 能在 Linux 内核上工作,这对解决我工作中使用的 Linux 的文件系统方面的问题很有帮助。此外,跟踪实用程序能帮助确定在系统调用期间文件系统实用程序什么地方出了故障。下次当您要摆平 Linux 中的错误时,请试试这些工具中的某一个。

2004年11月11日

1) IP包由包头和数据区组成, 包头由IP头(iphdr)和IP选项区组成, IP头的开始4位为版本号,
下4位为包头32比特字数, 因此IP包头最大长度为64字节, 最小长度为IP头长度(20字节),
数据区起始于32位边界上. 当包头长度大于IP头长度时, 从IP头开始的区域为IP选项区.

2) IP包接收器(ip_rcv)接收帧类型为ETH_P_IP(0×0800)的包, 它首先检查包的正确性,
再将输入包过滤器进行过滤, 再将过滤包绑定到输出路由, 分析其选项区,
最后从路由目的入口输出,
根据不同的路由类型, 输入到IP转发器(ip_forward)或IP本地分发器(ip_local_deliver)以及
IP同播转发器(ip_mc_output)中.

3) 如果系统允许接收带有信源路由选项(IPOPT_LSRR或IPOPT_SSRR)的IP包, 如果是本地包,
则将包重新绑定到信源路由表第1项地址所指定的路由上, 如果该地址是本地地址,
则用它替换IP头的目的地址并分析下一信源路由地址.

; net/ipv4/ip_output.c:

static struct packet_type ip_packet_type =
{
__constant_htons(ETH_P_IP),
NULL, /* All devices */
ip_rcv,
(void*)1,
NULL,
};
void __init ip_init(void)
{
dev_add_pack(&ip_packet_type);

ip_rt_init();
inet_initpeers();

#ifdef CONFIG_IP_MULTICAST
proc_net_create(“igmp”, 0, ip_mc_procinfo);
#endif
}

; net/ipv4/ip_input.c:
/*
* Main IP Receive routine.
*/
int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt)
{
struct iphdr *iph = skb->nh.iph;

/* When the interface is in promisc. mode, drop all the crap
* that it receives, do not try to analyse it.
*/
if (skb->pkt_type == PACKET_OTHERHOST)
goto drop;

IP_INC_STATS_BH(IpInReceives);

if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL)
goto out;

/*
* RFC1122: 3.1.2.2 MUST silently discard any IP frame that fails the checksum.
*
* Is the datagram acceptable?
*
* 1. Length at least the size of an ip header
* 2. Version of 4
* 3. Checksums correctly. [Speed optimisation for later, skip loopback checksums]
* 4. Doesn’t have a bogus length
*/

if (skb->len < sizeof(struct iphdr) || skb->len < (iph->ihl<<2))
goto inhdr_error;
if (iph->ihl < 5 || iph->version != 4 || ip_fast_csum((u8 *)iph, iph->ihl) != 0)
goto inhdr_error;

{
__u32 len = ntohs(iph->tot_len);
if (skb->len < len || len < (iph->ihl<<2))
goto inhdr_error;

/* Our transport medium may have padded the buffer out. Now we know it
* is IP we can trim to the true length of the frame.
* Note this now means skb->len holds ntohs(iph->tot_len).
*/
__skb_trim(skb, len);
}

return NF_HOOK(PF_INET, NF_IP_PRE_ROUTING, skb, dev, NULL,
       ip_rcv_finish);

inhdr_error:
IP_INC_STATS_BH(IpInHdrErrors);
drop:
        kfree_skb(skb);
out:
        return NET_RX_DROP;
}

#define IN_DEV_SOURCE_ROUTE(in_dev) (ipv4_devconf.accept_source_route &&
(in_dev)->cnf.accept_source_route)
#define IN_DEV_LOG_MARTIANS(in_dev) (ipv4_devconf.log_martians ||
(in_dev)->cnf.log_martians)

static inline int ip_rcv_finish(struct sk_buff *skb)
{
struct net_device *dev = skb->dev;
struct iphdr *iph = skb->nh.iph;

/*
* Initialise the virtual path cache for the packet. It describes
* how the packet travels inside Linux networking.
*/
if (skb->dst == NULL) {
if (ip_route_input(skb, iph->daddr, iph->saddr, iph->tos, dev))
goto drop;
}

#ifdef CONFIG_NET_CLS_ROUTE
if (skb->dst->tclassid) {
struct ip_rt_acct *st = ip_rt_acct + 256*smp_processor_id();
u32 idx = skb->dst->tclassid;
st[idx&0xFF].o_packets++;
st[idx&0xFF].o_bytes+=skb->len;
st[(idx>>16)&0xFF].i_packets++;
st[(idx>>16)&0xFF].i_bytes+=skb->len;
}
#endif

if (iph->ihl > 5) {
struct ip_options *opt;

/* It looks as overkill, because not all
   IP options require packet mangling.
   But it is the easiest for now, especially taking
   into account that combination of IP options
   and running sniffer is extremely rare condition.
                                      –ANK (980813)
*/

skb = skb_cow(skb, skb_headroom(skb));
if (skb == NULL)
return NET_RX_DROP;
iph = skb->nh.iph;

skb->ip_summed = 0;
if (ip_options_compile(NULL, skb))
goto inhdr_error;

opt = &(IPCB(skb)->opt);
if (opt->srr) {
struct in_device *in_dev = in_dev_get(dev);
if (in_dev) {
if (!IN_DEV_SOURCE_ROUTE(in_dev)) {
if (IN_DEV_LOG_MARTIANS(in_dev) && net_ratelimit())
printk(KERN_INFO “source route option %u.%u.%u.%u -> %u.%u.%u.%u\n”,
       NIPQUAD(iph->saddr), NIPQUAD(iph->daddr));
in_dev_put(in_dev);
goto drop;
}
in_dev_put(in_dev);
}
if (ip_options_rcv_srr(skb))
goto drop;
}
}

return skb->dst->input(skb);

inhdr_error:
IP_INC_STATS_BH(IpInHdrErrors);
drop:
        kfree_skb(skb);
        return NET_RX_DROP;
}


未完待续

2004年10月29日
LINUX2.4.x网络安全框架

作者:林风

1.概述

在分析LINUX2.4.x网络安全的实现之前先简单介绍一下它里面包含的几个重要概念:netfilter、iptables、match、target、nf_sockopt_ops、网络安全功能点的实现。详细解释会在后面的分析中讲到。

首先是netfilter,它定义了协议栈中的检查点和在检查点上引用的数据结构,以及在检查点上对这些结构引用的过程。iptables定义了实现网络安全功能的规则的组织以及对规则的操作。一个规则中包含零个或多个match和一个target,规则组织沿用了LINUX2.2.x中的chain,rule的概念,但是增加了table的概念,这三者的关系是:table是实现某项功能所有规则的总和,chain是在某个检查点上所引用规则的集合,rule是一个单独的规则。match在规则中用于匹配数据包中的各项参数,每个match匹配特定的参数,所以一个规则中可以有多个match,这包括系统已定义的match,也包括通过内核模块另外添加的match。target在规则中决定如何处理匹配到的数据包,因此在target中实现了具体的网络安全功能。nf_sockopt_ops是在系统调用get/setssockopt中引用的数据结构,实现用户空间对规则的添加、删除、修改、查询等动作。以上的结构在使用之前必须先注册到系统中才能被引用。

LINUX2.4.x网络安全实现了包过滤,地址转换(包含了LINUX2.2.x中的地址伪装和透明代理功能并有其他扩展功能),连接跟踪(这是实现地址转换的基础,在它里面实现了对连接状态的记录和监控,与状态检测类似),Mangle(这是LINUX2.4.x新增的一个功能,它对数据包进行检查但不做禁止、丢弃或允许的判断)。实现这些功能点需要分别注册netfilter,iptables,match,target,nf_sockopt_ops的数据结构。如果实现其他新的功能,只需定义相应的结构并将它注册到系统中,并且通过用户空间的配置工具(这个配置工具也须支持新的结构)把它加入到规则中就可以了。这些结构在规则中自动被引用。

2.netfilter

netfilter定义了协议栈中的检查点和检查点上引用的数据结构以及对这些数据结构引用的过程。首先看看在检查点上引用的数据结构,如图所示:

图2.1 nf_hoo_ops数据结构的组织

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


struct nf_hook_ops { struct list_head list; nf_hookfn hook; /* 函数指针 */ int pf; /* 结构对应的协议栈号 */ int hooknum; /* 结构对应的检查点号*/ int priority; /* 结构的优先值 */ };

nf_register_hook函数将ns_hook_ops结构注册到这些链表上,链表的索引由结构中hooknum指定。同一链表上的结构按优先值由小到大排列。在检查点上引用这些结构时,以它们在链表上的先后顺序引用。

检查点由宏NF_HOOK定义。在检查点上,函数nf_hook_slow调用函数nf_iterate遍历对应链表并调用链表上的结构ns_hook_ops中定义的函数。如果结构中的函数返回NF_ACCEPT,则继续调用下一个结构中的函数;如果结构中的函数返回NF_DROP或NF_STOLEN或NF_QUEUE,则将这个值返回给nf_hook_slow;如果结构中的函数返回NF_REPEAT,则重复调用此结构上的函数;如果到了链表上的最后一个结构,则把这个结构中函数的返回值返回给ns_hook_slow。在ns_hook_slow中判断nf_iterate的返回值,如果是NF_ACCEPT,则允许数据包通过,并将数据包传递给协议栈中的下一个函数;如果是NF_DROP,则释放数据包,协议栈流程中断;如果是NF_STOLEN,同样中断协议栈的流程,但是没有释放这个数据包;如果是NF_QUEUE,则将这个包发送到用户空间处理,同时中断协议栈的流程。

检查点分布在协议栈的流程中,下图是IPV4中的检查点:

图2.2 IPV4中的检查点

图中检查点的名称如下:

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

表2.1 IPV4中检查点的名称

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

3. iptables

iptables实现对规则的管理和访问。它里面有几个重要的数据结构ipt_entry,ipt_match,ipt_target,ipt_table,用于构造规则表。还有一个重要的函数ipt_do_table,用于遍历规则表并处理规则表上的结构。

ipt_entry是规则的数据结构,如下:


struct ipt_entry { struct ipt_ip ip; unsigned int nfcache; u_int16_t target_offset; /* target在规则中的偏移 */ u_int16_t next_offset; /* 下一条规则的偏移 */ unsigned int comefrom; struct ipt_counters counters; /* 匹配规则的数据包的统计计数 */ unsigned char elems[0]; };

在ipt_entry中ipt_ip是一个基本的match,它是固定的,用于匹配数据包的源地址/源端口、目的地址/目的端口、协议等。其他的match按需要添加,个数并不固定,所以在ipt_entry有一个变长的字符数组保存规则中match的指针,这些指针指向系统中注册的match。每个规则有一个target,决定数据包完全匹配规则后怎样处理这个数据包,它也是一个指向系统注册的target的指针,并且也放在前面提到的变长字符数组中。ipt_entry中的target_offset是target在规则中的偏移,偏移是从规则的起始地址到target所在位置的长度,还有一个变量next_offset指示下一条规则偏移,它其实就是本条规则的长度。

前面提到在iptables中沿用了LINUX2.2.x中的chain和rule的概念,那么在ipt_entry中如何区分chain和rule的哪?

我们知道chain是某个检查点上检查的规则的集合。除了默认的chain外,还可以创建新的chain。在iptables中,同一个chain里的规则是连续存放的。默认的chain的最后一条规则的target是chain的policy。用户创建的chain的最后一条规则的target的调用返回值是NF_RETURN,遍历过程将返回原来的chain。规则中的target也可以指定跳转到某个用户创建的chain上,这时它的target是ipt_stardard_target,并且这个target的verdict值大于0。如果在用户创建的chain上没有找到匹配的规则,遍历过程将返回到原来chain的下一条规则上。

ipt_match用于匹配数据包的参数,如TCP数据包中的标志位,ICMP协议中的类型等,每个match所感兴趣的参数都不一样,所以一条规则可能有多个match。ipt_target决定在数据包完全匹配规则后应做什么样的处理。这两个在使用之间都必须先注册到系统的链表中才能被规则引用。对这两个数据结构不做过多分析,读者可以自行参考源代码。

ipt_table是规则表的数据结构,如下:


struct ipt_table { struct list_head list; char name[IPT_TABLE_MAXNAMELEN]; struct ipt_replace table; /* 用户空间传递的规则表 */ unsigned int valid_hooks; /* 有效的检查点置位*/ rwlock_t lock; struct ipt_table_info private; /* 规则表在内核中的存储结构 */ struct module *me; };

在ipt_table中,ipt_replace是用户空间配置程序传递给内核的规则表,这个规则表不能直接使用,必须先根据它里面包含的match和target的名称将match和target转换成在内核注册的match和target的指针,还有一项重要的工作是检查规则表中是否有循环,如果有循环,要给用户空间的配置程序报告错误。转换之后的规则表存储在ipt_table_info中。valid_hooks指示与这个表相关的检查点,并把相应的位置为1。一个table中可以有多个chain,chain分为系统默认的chain(与table注册的检查点对应)和用户创建的chain。所有的table都注册放在一个链表中,而chain和rule则用偏移值next_offset连接成一个单向链表。用户空间的配置工具在添加、删除规则之前先把内核中的规则表取到用户空间,然后在用户空间做添加或删除的动作,然后再将修改过的规则表传递到内核空间,由内核空间的函数完成后续的转换和检查。

函数ipt_do_table遍历table上的规则,其实这个函数的指针就保存在nf_hook_ops结构中,并在检查点上被调用。调用这个函数时须指定它遍历的table的指针和调用它的检查点的值。检查点的值用来定位table中默认chain的位置,前面我们提到,默认的chain是和检查点对应的,在检查点上检查对应chain的规则。遍历规则,如果找到匹配的规则,则调用这条规则的target中定义的函数,并将它的返回值返回给调用ipt_do_table的函数,如果没有找到匹配的规则,则调用默认chain上最后一条规则的target定义的函数,这个函数的返回值就是这个chain的policy。

4. nf_sockopt_ops

前面提到LINUX2.4.x网络安全框架支持多种协议。规则的配置和查询通过系统调用get/setsockopt完成。在调用这两个系统调用时,不同协议使用的参数不同,所以每个实现网络安全功能的协议栈都定义了自己的nf_sockopt_ops结构并把它注册系统的链表中。在调用get/setsockopt时根据不同的参数决定引用哪一个nf_sockopt_ops来完成真正的工作。

5.网络安全功能点的实现

在LINUX2.4.x中实现网络安全的功能点需要做以下几件事情:一是定义nf_hook_ops结构,并将它注册到netfilter中;二是定义iptable,match,target结构,并将它注册到iptables中,如果需要还须注册nf_sockopt_ops结构以便处理特殊的get/setsockopt参数。下图就是IPV4中的功能点注册到netfilter中的nf_hook_ops结构:

图5.1 IPV4的功能点在各检查点上注册的结构

(图中conntrack代表连接跟踪;Filter代表包过滤;NAT(src)代表源地址转换,NAT(dst)代表目的地址转换;Mangle是LINUX2.4.x中新增的一个功能,完成对数据包的检查,但是不对数据包做禁止或放行的判断,与Filter不同。Mangle在LINUX2.4.18之前的实现中只在NF_IP_PRE_ROUTING,NF_IP_LOCAL_OUT两个检查点上注册了nf_hook_ops结构,在LINUX2.4.18之后的实现中在五个检查点上都注册了nf_look_ops结构。)

图中在每个检查点上,nf_hook_ops结构按调用的先后顺序从上而下排列。可以看到相同的功能点在不同的检查点上它的调用顺序并不相同,这与功能点所做的动作有关。比如在NF_IP_LOCAL_IN上假设Conntrack在Filter之前,如果数据包的状态在Conntrack中被记录而在Filter中被禁止,那么与这个数据包相关的状态就不会完整,浪费了一个Conntrack的结构,所以应该先调用Filter,如果它的返回值是NF_ACCEPT才调用Conntrack。

功能点上注册的ipt_table,ipt_match,ipt_target,nf_sockopt_ops结构如下表所示:


功能点名称 ipt_table ipt_match ipt_target nf_sockopt_ops
Filter packet_filter      
Nat nat_table   ipt_snat_reg
ipt_dnat_reg
 
Conntrack       so_getorigdst
Mangle packet_mangler      

表5.1 功能点注册的数据结构

值得指出的是连接跟踪(Conntrack)没有注册任何规则表,说明它不需要规则来决定是否要作连接跟踪。同时它又注册了一个nf_sockopt_ops结构,这个结构处理参数SO_ORIGINAL_DST,用于得到透明代理的目的地址。有关地址转换和连接跟踪的详细分析会在以后的文章中介绍。

6. 小结

LINUX2.4.x中的网络安全实现比LINUX2.2.x有了明显的进步。首先是IPV4协议栈中的检查点的安排更加合理,避免在实现各功能点时不必要的函数调用。其次它的架构可扩展性很强。把功能实现和框架分离,使功能实现时不必修改框架而只是注册相应的结构就可完成。还有就是在这个网络安全框架中实现的功能也比LINUX2.2.x丰富,支持的协议栈的数量也比LINUX2.2.x多。在这里我们只分析了LINUX2.4.x网络安全实现的大概情况,具体的某个功能的实现会在后面的文章中分析,通过这些分析,我们将学会如何在LINUX2.4.x的网路安全框架中添加自己想要实现的功能,如何利用现有实现中的资源而有避免与现有的实现冲突。

LinuxHacker

表驱动IP过滤器的基本工作过程

表驱动IP过滤器的基本工作过程

(1) Linux的IP过滤器主要采用了表驱动方法, 表驱动入口函数为ipt_do_table(), 驱动表称为IP表, 用ip_table结构描述. IP表是位置无关的数据块, 它由表头(ipt_table_info)和变长的规则链组成. 表头包含了IP表的尺寸,规则数量和不同的过滤编号对应的规则项的起始位置. 规则链是由多个变长的规则项组成, 每个规则项可分为匹配链和匹配靶两部分, 匹配链由IP匹配模板(ipt_entry)和可选的多个扩展匹配操作项组成, 匹配项由匹配结构(ipt_entry_match)和变长参数区组成, 匹配结构包含指向匹配器(ipt_match)模块的指针. 匹配靶则由靶结构(ipt_entry_target)和变长参数区组成, 靶结构包含指向靶处理(ipt_target)模块的指针.

(2) ipt_do_table()对输入的IP包首先沿着IP表规则项的链头IP模板进行匹配, 当规则头匹配成功时, 如果存在后继扩展匹配链, 则进行链式扩展匹配. 当所有的匹配都成功后, 则运行相应的规则靶, 如果规则靶函数返回IPT_CONTINUE, 则继续表中下一规则项匹配, 否则过滤过程结束并返回. 如果靶函数为空, 则说明它是一个标准靶(ipt_standard_target), 标准靶的参数区具有一个判别字(verdict), 它的值如果非负则表示它是一个转移靶, 指向另一个规则项. 如果判别字为负且等于IPT_RETURN, 则说明标准靶是一转移返回靶, 否则将判别字取反减一后作为过滤器的返回值中止过滤过程.

; include/linux/netfilter_ipv4/ip_tables.h:

#define IPT_TABLE_MAXNAMELEN 32

/* Furniture shopping… */
struct ipt_table 驱动表描述结构
{
struct list_head list;

/* A unique name… */
char name[IPT_TABLE_MAXNAMELEN];

/* Seed table: copied in register_table */
struct ipt_replace *table;

/* What hooks you will enter on */
unsigned int valid_hooks;

/* Lock for the curtain */
rwlock_t lock;

/* Man behind the curtain… */
struct ipt_table_info *private; 指向IP表
};

/* This structure defines each of the firewall rules. Consists of 3
parts which are 1) general IP header stuff 2) match specific
stuff 3) the target to perform if the rule matches */
struct ipt_entry 规则项起始匹配单元
{
struct ipt_ip ip; IP包匹配模板

/* Mark with fields that we care about. */
unsigned int nfcache;

/* Size of ipt_entry + matches */
u_int16_t target_offset; 匹配链尺寸
/* Size of ipt_entry + matches + target */
u_int16_t next_offset; 规则项尺寸

/* Back pointer */
unsigned int comefrom;

/* Packet and byte counters. */
struct ipt_counters counters;

/* The matches (if any), then the target. */
unsigned char elems[0];
};
struct ipt_counters
{
u_int64_t pcnt, bcnt; /* Packet and byte counters */
};

/* Yes, Virginia, you have to zero the padding. */
struct ipt_ip { IP头匹配模板
/* Source and destination IP addr */
struct in_addr src, dst;
/* Mask for src and dest IP addr */
struct in_addr smsk, dmsk;
char iniface[IFNAMSIZ], outiface[IFNAMSIZ];
unsigned char iniface_mask[IFNAMSIZ], outiface_mask[IFNAMSIZ];

/* Protocol, 0 = ANY */
u_int16_t proto;

/* Flags word */
u_int8_t flags;
/* Inverse flags */
u_int8_t invflags; 反匹配标志集
};

#define IPT_FUNCTION_MAXNAMELEN 30

struct ipt_entry_match 匹配操作结构
{
union {
struct {
u_int16_t match_size;

/* Used by userspace */
char name[IPT_FUNCTION_MAXNAMELEN];
} user;
struct {
u_int16_t match_size;

/* Used inside the kernel */
struct ipt_match *match;
} kernel;

/* Total length */
u_int16_t match_size;
} u;

unsigned char data[0];
};
struct ipt_match 匹配器
{
struct list_head list;

const char name[IPT_FUNCTION_MAXNAMELEN];

/* Return true or false: return FALSE and set *hotdrop = 1 to
force immediate packet drop. */
int (*match)(const struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
const void *matchinfo,
int offset,
const void *hdr,
u_int16_t datalen,
int *hotdrop);

/* Called when user tries to insert an entry of this type. */
/* Should return true or false. */
int (*checkentry)(const char *tablename,
const struct ipt_ip *ip,
void *matchinfo,
unsigned int matchinfosize,
unsigned int hook_mask);

/* Called when entry of this type deleted. */
void (*destroy)(void *matchinfo, unsigned int matchinfosize);

/* Set this to THIS_MODULE if you are a module, otherwise NULL */
struct module *me;
};

struct ipt_entry_target 匹配靶
{
union {
struct {
u_int16_t target_size;

/* Used by userspace */
char name[IPT_FUNCTION_MAXNAMELEN];
} user;
struct {
u_int16_t target_size;

/* Used inside the kernel */
struct ipt_target *target;
} kernel;

/* Total length */
u_int16_t target_size;
} u;

unsigned char data[0];
};

/* Registration hooks for targets. */
struct ipt_target 靶处理器
{
struct list_head list;

const char name[IPT_FUNCTION_MAXNAMELEN];

/* Returns verdict. */
unsigned int (*target)(struct sk_buff **pskb,
unsigned int hooknum,
const struct net_device *in,
const struct net_device *out,
const void *targinfo,
void *userdata);

/* Called when user tries to insert an entry of this type:
hook_mask is a bitmask of hooks from which it can be
called. */
/* Should return true or false. */
int (*checkentry)(const char *tablename,
const struct ipt_entry *e,
void *targinfo,
unsigned int targinfosize,
unsigned int hook_mask);

/* Called when entry of this type deleted. */
void (*destroy)(void *targinfo, unsigned int targinfosize);

/* Set this to THIS_MODULE if you are a module, otherwise NULL */
struct module *me;
};

struct ipt_standard_target 标准靶
{
struct ipt_entry_target target;
int verdict; 判别字
};

/* The table itself */
struct ipt_table_info IP表表头结构
{
/* Size per table */
unsigned int size;
/* Number of entries: FIXME. –RR */
unsigned int number;

/* Entry points and underflows */
unsigned int hook_entry[NF_IP_NUMHOOKS];
unsigned int underflow[NF_IP_NUMHOOKS];

/* ipt_entry tables: one per CPU */
char entries[0] __attribute__((aligned(SMP_CACHE_BYTES)));
};

/* fn returns 0 to continue iteration */
#define IPT_MATCH_ITERATE(e, fn, args…)
({
unsigned int __i;
int __ret = 0;
struct ipt_entry_match *__m;

for (__i = sizeof(struct ipt_entry);
__i < (e)->target_offset;
__i += __m->u.match_size) {
__m = (void *)(e) + __i;

__ret = fn(__m , ## args);
if (__ret != 0)
break;
}
__ret;
})

/* Standard return verdict, or do jump. */
#define IPT_STANDARD_TARGET “”
/* Error verdict. */
#define IPT_ERROR_TARGET “ERROR”

; net/ipv4/netfilter/ip_tables.c:

static LIST_HEAD(ipt_target);
static LIST_HEAD(ipt_match);
static LIST_HEAD(ipt_tables);

/* The built-in targets: standard (NULL) and error. */
static struct ipt_target ipt_standard_target
= { { NULL, NULL }, IPT_STANDARD_TARGET, NULL, NULL, NULL };
static struct ipt_target ipt_error_target
= { { NULL, NULL }, IPT_ERROR_TARGET, ipt_error, NULL, NULL };

static struct nf_sockopt_ops ipt_sockopts
= { { NULL, NULL }, PF_INET, IPT_BASE_CTL, IPT_SO_SET_MAX+1, do_ipt_set_ctl,
IPT_BASE_CTL, IPT_SO_GET_MAX+1, do_ipt_get_ctl, 0, NULL };

static struct ipt_match tcp_matchstruct
= { { NULL, NULL }, “tcp”, &tcp_match, &tcp_checkentry, NULL };
static struct ipt_match udp_matchstruct
= { { NULL, NULL }, “udp”, &udp_match, &udp_checkentry, NULL };
static struct ipt_match icmp_matchstruct
= { { NULL, NULL }, “icmp”, &icmp_match, &icmp_checkentry, NULL };

static int __init init(void)
{
int ret;

/* Noone else will be downing sem now, so we won’t sleep */
down(&ipt_mutex);
list_append(&ipt_target, &ipt_standard_target); 注册标准靶处理器
list_append(&ipt_target, &ipt_error_target); 注册出错靶处理器
list_append(&ipt_match, &tcp_matchstruct); 注册TCP包匹配器
list_append(&ipt_match, &udp_matchstruct); 注册UDP包匹配器
list_append(&ipt_match, &icmp_matchstruct); 注册ICMP包匹配器
up(&ipt_mutex);

/* Register setsockopt */
ret = nf_register_sockopt(&ipt_sockopts); 注册IP表用户接口
if (ret < 0) {
duprintf(“Unable to register sockopts. “);
return ret;
}

#ifdef CONFIG_PROC_FS
if (!proc_net_create(“ip_tables_names”, 0, ipt_get_tables)) {
nf_unregister_sockopt(&ipt_sockopts);
return -ENOMEM;
}
#endif

printk(“ip_tables: (c)2000 Netfilter core team “);
return 0;
}

/* Returns one of the generic firewall policies, like NF_ACCEPT. */
unsigned int
ipt_do_table(struct sk_buff **pskb,
unsigned int hook,
const struct net_device *in,
const struct net_device *out,
struct ipt_table *table,
void *userdata) 表驱动过滤器的入口函数
{
static const char nulldevname[IFNAMSIZ] = { 0 };
u_int16_t offset;
struct iphdr *ip;
void *protohdr;
u_int16_t datalen;
int hotdrop = 0;
/* Initializing verdict to NF_DROP keeps gcc happy. */
unsigned int verdict = NF_DROP;
const char *indev, *outdev;
void *table_base;
struct ipt_entry *e, *back;

/* Initialization */
ip = (*pskb)->nh.iph; 取IP头
protohdr = (u_int32_t *)ip + ip->ihl; 取传输头
datalen = (*pskb)->len – ip->ihl * 4; 取传输块长度
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. */
offset = ntohs(ip->frag_off) & IP_OFFSET; 取IP包片段所在的偏移

read_lock_bh(&table->lock);
IP_NF_ASSERT(table->valid_hooks & (1 << hook));
table_base = (void *)table->private->entries
+ TABLE_OFFSET(table->private,
cpu_number_map(smp_processor_id())); 取过滤表的规则区
e = get_entry(table_base, table->private->hook_entry[hook]); 取过滤编号对应的起始规则项

#ifdef CONFIG_NETFILTER_DEBUG
/* Check noone else using our table */
if (((struct ipt_entry *)table_base)->comefrom != 0xdead57ac
&& ((struct ipt_entry *)table_base)->comefrom != 0xeeeeeeec) {
printk(“ASSERT: CPU #%u, %s comefrom(%p) = %X “,
smp_processor_id(),
table->name,
&((struct ipt_entry *)table_base)->comefrom,
((struct ipt_entry *)table_base)->comefrom);
}
((struct ipt_entry *)table_base)->comefrom = 0×57acc001;
#endif

/* For return from builtin chain */
back = get_entry(table_base, table->private->underflow[hook]);

do {
IP_NF_ASSERT(e);
IP_NF_ASSERT(back);
(*pskb)->nfcache |= e->nfcache;
if (ip_packet_match(ip, indev, outdev, &e->ip, offset)) { 匹配IP头成功
struct ipt_entry_target *t;

if (IPT_MATCH_ITERATE(e, do_match, 对匹配链进行链式匹配
*pskb, in, out,
offset, protohdr,
datalen, &hotdrop) != 0)
goto no_match;

ADD_COUNTER(e->counters, ntohs(ip->tot_len), 1); 刷新规则项中包和字节计数器

t = ipt_get_target(e); 取规则项的靶结构
IP_NF_ASSERT(t->u.kernel.target);
/* Standard target? */
if (!t->u.kernel.target->target) { 如果是标准靶
int v;

v = ((struct ipt_standard_target *)t)->verdict;
if (v < 0) {
/* Pop from stack? */
if (v != IPT_RETURN) { 如果不是返回靶
verdict = (unsigned)(-v) – 1; 求过滤器返回值
break; 退出过滤器
}
e = back;
back = get_entry(table_base,
back->comefrom);
continue;
}
if (table_base + v
!= (void *)e + e->next_offset) { 如果是转移靶
/* Save old back ptr in next entry */
struct ipt_entry *next
= (void *)e + e->next_offset;
next->comefrom
= (void *)back – table_base;
/* set back pointer to next entry */
back = next; 设置返回点
}

e = get_entry(table_base, v);
} else {
/* Targets which reenter must return
abs. verdicts */
#ifdef CONFIG_NETFILTER_DEBUG
((struct ipt_entry *)table_base)->comefrom
= 0xeeeeeeec;
#endif
verdict = t->u.kernel.target->target(pskb,
hook,
in, out,
t->data,
userdata); 调用靶函数

#ifdef CONFIG_NETFILTER_DEBUG
if (((struct ipt_entry *)table_base)->comefrom
!= 0xeeeeeeec
&& verdict == IPT_CONTINUE) {
printk(“Target %s reentered! “,
t->u.kernel.target->name);
verdict = NF_DROP;
}
((struct ipt_entry *)table_base)->comefrom
= 0×57acc001;
#endif
/* Target might have changed stuff. */
ip = (*pskb)->nh.iph;
protohdr = (u_int32_t *)ip + ip->ihl;
datalen = (*pskb)->len – ip->ihl * 4;

if (verdict == IPT_CONTINUE) 继续下一规则项匹配
e = (void *)e + e->next_offset;
else
/* Verdict */
break;
}
} else { 规则项匹配不成功

no_match:
e = (void *)e + e->next_offset; 取下一规则项
}
} while (!hotdrop);

#ifdef CONFIG_NETFILTER_DEBUG
((struct ipt_entry *)table_base)->comefrom = 0xdead57ac;
#endif
read_unlock_bh(&table->lock);

#ifdef DEBUG_ALLOW_ALL
return NF_ACCEPT;
#else
if (hotdrop)
return NF_DROP;
else return verdict;
#endif
}

/* Returns whether matches rule or not. */
static inline int
ip_packet_match(const struct iphdr *ip,
const char *indev,
const char *outdev,
const struct ipt_ip *ipinfo,
int isfrag) IP头匹配器
{
size_t i;
unsigned long ret;

#define FWINV(bool,invflg) ((bool) ^ !!(ipinfo->invflags & invflg)) 如果模板中具有invflg标志, 则bool值取反

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. “);

dprintf(“SRC: %u.%u.%u.%u. Mask: %u.%u.%u.%u. Target: %u.%u.%u.%u.%s “,
NIPQUAD(ip->saddr),
NIPQUAD(ipinfo->smsk.s_addr),
NIPQUAD(ipinfo->src.s_addr),
ipinfo->invflags & IPT_INV_SRCIP ? ” (INV)” : “”);
dprintf(“DST: %u.%u.%u.%u Mask: %u.%u.%u.%u Target: %u.%u.%u.%u.%s “,
NIPQUAD(ip->daddr),
NIPQUAD(ipinfo->dmsk.s_addr),
NIPQUAD(ipinfo->dst.s_addr),
ipinfo->invflags & IPT_INV_DSTIP ? ” (INV)” : “”);
return 0;
}

/* Look for ifname matches; this should unroll nicely. */
for (i = 0, ret = 0; i < IFNAMSIZ/sizeof(unsigned long); i++) { 匹配输入设备名模板
ret |= (((const unsigned long *)indev)
^ ((const unsigned long *)ipinfo->iniface))
& ((const unsigned long *)ipinfo->iniface_mask);
}

if (FWINV(ret != 0, IPT_INV_VIA_IN)) {
dprintf(“VIA in mismatch (%s vs %s).%s “,
indev, ipinfo->iniface,
ipinfo->invflags&IPT_INV_VIA_IN ?” (INV)”:”");
return 0;
}

for (i = 0, ret = 0; i < IFNAMSIZ/sizeof(unsigned long); i++) { 匹配输出设备名模板
ret |= (((const unsigned long *)outdev)
^ ((const unsigned long *)ipinfo->outiface))
& ((const unsigned long *)ipinfo->outiface_mask);
}

if (FWINV(ret != 0, IPT_INV_VIA_OUT)) {
dprintf(“VIA out mismatch (%s vs %s).%s “,
outdev, ipinfo->outiface,
ipinfo->invflags&IPT_INV_VIA_OUT ?” (INV)”:”");
return 0;
}

/* Check specific protocol */
if (ipinfo->proto 匹配IP传输协议码
&& FWINV(ip->protocol != ipinfo->proto, IPT_INV_PROTO)) {
dprintf(“Packet protocol %hi does not match %hi.%s “,
ip->protocol, ipinfo->proto,
ipinfo->invflags&IPT_INV_PROTO ? ” (INV)”:”");
return 0;
}

/* If we have a fragment rule but the packet is not a fragment
* then we return zero */ 匹配IP包是否允许分片
if (FWINV((ipinfo->flags&IPT_F_FRAG) && !isfrag, IPT_INV_FRAG)) {
dprintf(“Fragment rule but not fragment.%s “,
ipinfo->invflags & IPT_INV_FRAG ? ” (INV)” : “”);
return 0;
}

return 1;
}
static inline
int do_match(struct ipt_entry_match *m,
const struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
int offset,
const void *hdr,
u_int16_t datalen,
int *hotdrop) 链式匹配中的匹配器接口
{
/* Stop iteration if it doesn’t match */
if (!m->u.kernel.match->match(skb, in, out, m->data, /* m->data为匹配项中的参数区 */
offset, hdr, datalen, hotdrop))
return 1;
else
return 0;
}

static unsigned int
ipt_error(struct sk_buff **pskb,
unsigned int hooknum,
const struct net_device *in,
const struct net_device *out,
const void *targinfo,
void *userinfo) 错误靶函数
{
if (net_ratelimit())
printk(“ip_tables: error: `%s’ “, (char *)targinfo);

return NF_DROP;
}
/* TCP matching stuff */
struct ipt_tcp TCP匹配操作项参数区
{
u_int16_t spts[2]; /* Source port range. */
u_int16_t dpts[2]; /* Destination port range. */
u_int8_t option; /* TCP Option iff non-zero*/
u_int8_t flg_mask; /* TCP flags mask byte */
u_int8_t flg_cmp; /* TCP flags compare byte */
u_int8_t invflags; /* Inverse flags */
};
static int
tcp_match(const struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
const void *matchinfo,
int offset,
const void *hdr,
u_int16_t datalen,
int *hotdrop) TCP匹配器函数
{
const struct tcphdr *tcp = hdr;
const struct ipt_tcp *tcpinfo = matchinfo;

/* To quote Alan:

Don’t allow a fragment of TCP 8 bytes in. Nobody normal
causes this. Its a cracker trying to break in by doing a
flag overwrite to pass the direction checks.
*/

if (offset == 1) {
duprintf(“Dropping evil TCP offset=1 frag. “);
*hotdrop = 1;
return 0;
} else if (offset == 0 && datalen < sizeof(struct tcphdr)) {
/* We’ve been asked to examine this packet, and we
can’t. Hence, no choice but to drop. */
duprintf(“Dropping evil TCP offset=0 tinygram. “);
*hotdrop = 1;
return 0;
}

/* FIXME: Try tcp doff >> packet len against various stacks –RR */

#define FWINVTCP(bool,invflg) ((bool) ^ !!(tcpinfo->invflags & invflg))

/* Must not be a fragment. */
return !offset
&& port_match(tcpinfo->spts[0], tcpinfo->spts[1],
ntohs(tcp->source),
!!(tcpinfo->invflags & IPT_TCP_INV_SRCPT)) TCP源端口号是否在范围内
&& port_match(tcpinfo->dpts[0], tcpinfo->dpts[1],
ntohs(tcp->dest),
!!(tcpinfo->invflags & IPT_TCP_INV_DSTPT)) TCP目的端口号是否在范围内
&& FWINVTCP((((unsigned char *)tcp)[13]
& tcpinfo->flg_mask)
== tcpinfo->flg_cmp,
IPT_TCP_INV_FLAGS) 匹配TCP标志码
&& (!tcpinfo->option
|| tcp_find_option(tcpinfo->option, tcp, datalen,
tcpinfo->invflags
& IPT_TCP_INV_OPTION,
hotdrop)); 是否允许特定的TCP选项
}

/* Called when user tries to insert an entry of this type. */
static int
tcp_checkentry(const char *tablename,
const struct ipt_ip *ip,
void *matchinfo,
unsigned int matchsize,
unsigned int hook_mask) TCP匹配器参数初始化校验函数
{
const struct ipt_tcp *tcpinfo = matchinfo;

/* Must specify proto == TCP, and no unknown invflags */
return ip->proto == IPPROTO_TCP
&& !(ip->invflags & IPT_INV_PROTO)
&& matchsize == IPT_ALIGN(sizeof(struct ipt_tcp))
&& !(tcpinfo->invflags & ~IPT_TCP_INV_MASK);
}

/* UDP matching stuff */
struct ipt_udp
{
u_int16_t spts[2]; /* Source port range. */
u_int16_t dpts[2]; /* Destination port range. */
u_int8_t invflags; /* Inverse flags */
};
static int
udp_match(const struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
const void *matchinfo,
int offset,
const void *hdr,
u_int16_t datalen,
int *hotdrop) UDP匹配器函数
{
const struct udphdr *udp = hdr;
const struct ipt_udp *udpinfo = matchinfo;

if (offset == 0 && datalen < sizeof(struct udphdr)) {
/* We’ve been asked to examine this packet, and we
can’t. Hence, no choice but to drop. */
duprintf(“Dropping evil UDP tinygram. “);
*hotdrop = 1;
return 0;
}

/* Must not be a fragment. */
return !offset
&& port_match(udpinfo->spts[0], udpinfo->spts[1],
ntohs(udp->source),
!!(udpinfo->invflags & IPT_UDP_INV_SRCPT))
&& port_match(udpinfo->dpts[0], udpinfo->dpts[1],
ntohs(udp->dest),
!!(udpinfo->invflags & IPT_UDP_INV_DSTPT));
}

/* Called when user tries to insert an entry of this type. */
static int
udp_checkentry(const char *tablename,
const struct ipt_ip *ip,
void *matchinfo,
unsigned int matchinfosize,
unsigned int hook_mask)
{
const struct ipt_udp *udpinfo = matchinfo;

/* Must specify proto == UDP, and no unknown invflags */
if (ip->proto != IPPROTO_UDP || (ip->invflags & IPT_INV_PROTO)) {
duprintf(“ipt_udp: Protocol %u != %u “, ip->proto,
IPPROTO_UDP);
return 0;
}
if (matchinfosize != IPT_ALIGN(sizeof(struct ipt_udp))) {
duprintf(“ipt_udp: matchsize %u != %u “,
matchinfosize, IPT_ALIGN(sizeof(struct ipt_udp)));
return 0;
}
if (udpinfo->invflags & ~IPT_UDP_INV_MASK) {
duprintf(“ipt_udp: unknown flags %X “,
udpinfo->invflags);
return 0;
}

return 1;
}

/* ICMP matching stuff */
struct ipt_icmp
{
u_int8_t type; /* type to match */
u_int8_t code[2]; /* range of code */
u_int8_t invflags; /* Inverse flags */
};
static int
icmp_match(const struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
const void *matchinfo,
int offset,
const void *hdr,
u_int16_t datalen,
int *hotdrop)
{
const struct icmphdr *icmp = hdr;
const struct ipt_icmp *icmpinfo = matchinfo;

if (offset == 0 && datalen < 2) {
/* We’ve been asked to examine this packet, and we
can’t. Hence, no choice but to drop. */
duprintf(“Dropping evil ICMP tinygram. “);
*hotdrop = 1;
return 0;
}

/* Must not be a fragment. */
return !offset
&& icmp_type_code_match(icmpinfo->type,
icmpinfo->code[0],
icmpinfo->code[1],
icmp->type, icmp->code,
!!(icmpinfo->invflags&IPT_ICMP_INV)); 匹配具有特定类型,控制码在某个范围内的ICMP包
}
/* Returns 1 if the type and code is matched by the range, 0 otherwise */
static inline int
icmp_type_code_match(u_int8_t test_type, u_int8_t min_code, u_int8_t max_code,
u_int8_t type, u_int8_t code,
int invert)
{
return (type == test_type && code >= min_code && code <= max_code)
^ invert;
}

/* Called when user tries to insert an entry of this type. */
static int
icmp_checkentry(const char *tablename,
const struct ipt_ip *ip,
void *matchinfo,
unsigned int matchsize,
unsigned int hook_mask)
{
const struct ipt_icmp *icmpinfo = matchinfo;

/* Must specify proto == ICMP, and no unknown invflags */
return ip->proto == IPPROTO_ICMP
&& !(ip->invflags & IPT_INV_PROTO)
&& matchsize == IPT_ALIGN(sizeof(struct ipt_icmp))
&& !(icmpinfo->invflags & ~IPT_ICMP_INV);
}
/* Returns 1 if the port is matched by the range, 0 otherwise */
static inline int
port_match(u_int16_t min, u_int16_t max, u_int16_t port, int invert)
{
int ret;

ret = (port >= min && port <= max) ^ invert;
return ret;
}
static int
tcp_find_option(u_int8_t option,
const struct tcphdr *tcp,
u_int16_t datalen,
int invert,
int *hotdrop)
{
unsigned int i = sizeof(struct tcphdr);
const u_int8_t *opt = (u_int8_t *)tcp;

duprintf(“tcp_match: finding option “);
/* If we don’t have the whole header, drop packet. */
if (tcp->doff * 4 > datalen) {
*hotdrop = 1;
return 0;
}

while (i < tcp->doff * 4) {
if (opt == option) return !invert;
if (opt < 2) i++;
else i += opt[i+1]?:1;
}

return invert;
}
static inline struct ipt_entry *
get_entry(void *base, unsigned int offset)
{
return (struct ipt_entry *)(base + offset);
}
/* Append. */
extern inline void
list_append(struct list_head *head, void *new)
{
ASSERT_WRITE_LOCK(head);
list_add((new), (head)->prev);
}

2004年10月27日

Jobs did today:
1) Read the chapter 6 named “How to construct a rule in iptables” of the document “Guids of iptables 1.1.19″. Learned some arguments and options of iptables.
2) Analysed a kernel programme which is used to sniff the FTP password.
3) Learned some knowleadge about difference between kernel address space and user address space.

Questions met today:
The data structures which iptables used is too much. Are there some better methods to master them?

Jobs did today:

1) Read the first four chapters of “Guids of iptables 1.1.19″ which gives an entir introduction about iptables.
2) Analyse some kernel data structures which are used by the kernel completment of iptables.
3) make a test to make sure if it could get the same address of icmp header between the method “(struct icmphdr *)(skb->data+skb->nh.iph->ihl*4)” and skb->h.icmph. The result is they are different.

Jobs did:
1) Reading some documents about hacking the system call.
2) Attending the discussion about how to make a module using netfilter.

Job I did:
1)Read parts of the document named “Analysis In Linux Kernel Network Stack”. The part what I read was the introduction about several methods of filtering the packets based on netfilter and iptables.
2)Read articles about how to make a new module to replace the system call of Linux. I studied an example of replacing the __NR_getdents system call which will be called when users execute the command “ls”.
3)Write a programme which will display the infomations of the packet going through the host.
Questions I met:
1)when reading the module replacing the system call, I couldn’t understand some functions the module used such as memcpy_fromfs.