开始:pcap应用程序的格式
我们所要理解的第一件事情是一个基于pcap的嗅探器程序的总体布局。流程如下:
1.我们从决定用哪一个接口进行嗅探开始。在Linux中,这可能是eth0,而在BSD系统中则可能是xl1等等。我们也可以用一个字符串来定义这个设备,或者采用pcap提供的接口名来工作。
2.初始化pcap。在这里我们要告诉pcap对什么设备进行嗅探。假如愿意的话,我们还可以嗅探多个设备。怎样区分它们呢?使用 文件句柄。就像打开一个文件进行读写一样,必须命名我们的嗅探“会话”,以此使它们各自区别开来。
3.如果我们只想嗅探特定的传输(如TCP/IP包,发往端口23的包等等),我们必须创建一个规则集合,编译并且使用它。这个过程分为三个相互紧密关联的阶段。规则集合被置于一个字符串内,并且被转换成能被pcap读的格式(因此编译它)。编译实际上就是在我们的程序里调用一个不被外部程序使用的函数。接下来我们要告诉 pcap使用它来过滤出我们想要的那一个会话。
4.最后,我们告诉pcap进入它的主体执行循环。在这个阶段内pcap一直工作到它接收了所有我们想要的包为止。每当它收到一个包就调用另一个已经定义好的函数,这个函数可以做我们想要的任何工作,它可以剖析所部获的包并给用户打印出结果,它可以将结果保存为一个文件,或者什么也不作。
5.在嗅探到所需的数据后,我们要关闭会话并结束。
这是实际上一个很简单的过程。一共五个步骤,其中一个(第3个)是可选的。我们为什么不看一看是怎样实现每一个步骤呢? 设置设备
这是很简单的。有两种方法设置想要嗅探的设备。
第一种,我们可以简单的让用户告诉我们。考察下面的程序: #include int main(int argc, char *argv[]) { char *dev = argv[1]; printf(\"Device: %s\return(0); } 用户通过传递给程序的第一个参数来指定设备。字符串“dev”以pcap能“理解”的格式保存了我们要嗅探的接口的名字(当然,用户必须给了我们一个真正存在的接口)。 另一种也是同样的简单。来看这段程序: #include char *dev, errbuf[PCAP_ERRBUF_SIZE]; dev = pcap_lookupdev(errbuf); printf(\"Device: %s\return(0); 1 } 在这个例子里,pcap就自己设置设备。“但是,等一下,Tim”,你会说,“字符串errbuf是做什么的?”大多数的pcap命令允许我们向它们传递字符串作为参数。这个字符串的目的是什么呢?如果命令失败,它将传给这个字符串关于错误的描述。这样,如果pcap_lookupdev()失败,它将在 errbuf存储错误信息。很好,是不是?这就是我们怎样去设置设备。 打开设备进行嗅探 创建一个嗅探会话的任务真的非常简单。为此,我们使用pcap_open_live()函数。此函数的原型(根据pcap的手册页)如下: pcap_t *pcap_open_live(char *device, int snaplen, int promisc, int to_ms, char *ebuf) 其第一个参数是我们在上一节中指定的设备,snaplen是整形的,它定义了将被pcap捕获的最大字节数。当promisc设为true时将置指定接口为混杂模式(然而,当它置为false时接口仍处于混杂模式的特殊情况也是有可能的)。to_ms是读取时的超时值,单位是毫秒(如果为0则一直嗅探直到错误发生,为-1则不确定)。最后,ebuf是一个我们可以存入任何错误信息的字符串(就像上面的errbuf)。此函数返回其会话句柄。 举个例子,考察以下代码片断: #include pcap_t *handle; handle = pcap_open_live(somedev, BUFSIZ, 1, 0, errbuf); 这个代码片断打开字符串somedev的设备,告诉它读取被BUFSIZ指定的字节数(BUFSIZ在pcap.h里定义)。我们告诉它将设备置为混杂模式,一直嗅探到错误发生,如果有了错误,把它存放在字符串errbuf中。 混杂模式与非混杂模式的区别:这两种方式区别很大。一般来说,非混杂模式的嗅探器中,主机仅嗅探那些跟它直接有关的通信,如发向它的,从它发出的,或经它路由的等都会被嗅探器捕获。而在混杂模式中则嗅探传输线路上的所有通信。在非交换式网络中,这将是整个网络的通信。这样做最明显的优点就是使更多的包被嗅探到,它们因你嗅探网络的原因或者对你有帮助,或者没有。但是,混杂模式是可被探测到的。一个主机可以通过高强度的测试判定另一台主机是否正在进行混杂模式的嗅探。其次,它仅在非交换式的网络环境中有效工作(如集线器,或者交换中的ARP层面)。再次,在高负荷的网络中,主机的系统资源将消耗的非常严重。 过滤通信 通常,我们的嗅探器仅对某特定的通信感兴趣。例如,有时我们想嗅探到端口23(telnet)的包以获得密码;或者我们想截获一个正通过端口21 (FTP)传送的文件;可能我们仅想要得到DNS的通信(端口53,UDP)。无论哪种情况,我们都很少盲目的嗅探整个网络的通信。下面讨论pcap_compile()与pcap_setfilter()。 这个过程非常简单。当我们已经调用了pcap_open_live()从而建立了一个嗅探会话之后就可以应用我们自己的过滤器了。为什么要用我们自己的过滤器呢?有两个原因。第一,pcap的过滤器太强大了,因为它直接使用 BPF过滤器,我们通过使用BPF驱动直接过滤跳过了很多的关节。第二,这样做要容易的多。 在使用我们自己的过滤器前必须编译它。过滤表达式被保存在一个字符串中(字符数组)。其句法在tcpdump的手册页中被证明非常好。我建议你亲自阅读它。但是我们将使用简单的测试表达式,这样你可能很容易理解我的例子。 我们调用pcap_compile()来编译它,其原型是这样定义的: 2 int pcap_compile(pcap_t *p, struct bpf_program *fp, char *str, int optimize, bpf_u_int32 netmask) 第 一个参数是会话句柄(pcap_t *handle在前一节的示例中)。接下来的是我们存储被编译的过滤器版本的地址的引用。再接下来的则是表达式本身,存储在规定的字符串格式里。再下边是一个定义表达式是否被优化的整形量(0为false,1为true,标准规定)。最后,我们必须指定应用此过滤器的网络掩码。函数返回-1为失败,其他的任何值都表明是成功的。 表达式被编译之后就可以使用了。现在进入pcap_setfilter()。仿照我们介绍pcap的格式,先来看一看pcap_setfilter()的原型: int pcap_setfilter(pcap_t *p, struct bpf_program *fp) 这非常直观,第一个参数是会话句柄,第二个参数是被编译表达式版本的引用(可推测出它与pcap_compile()的第二个参数相同)。 下面的代码示例可能能使你更好的理解: #include pcap_t *handle; /* 会话的句柄 */ char dev[] = \"rl0\"; /* 执行嗅探的设备 */ char errbuf[PCAP_ERRBUF_SIZE]; /* 存储错误 信息的字符串 */ struct bpf_program filter; /*已经编译好的过滤表达式*/ char filter_app[] = \"port 23\"; /* 过滤表达式*/ bpf_u_int32 mask; /* 执行嗅探的设备的网络掩码 */ bpf_u_int32 net; /* 执行嗅探的设备的IP地址 */ pcap_lookupnet(dev, &net, &mask, errbuf); handle = pcap_open_live(dev, BUFSIZ, 1, 0, errbuf); pcap_compile(handle, &filter, filter_app, 0, net); pcap_setfilter(handle, &filter); 这个程序使嗅探器嗅探经由端口23的所有通信,使用混杂模式,设备是rl0。 你可能注意到前面的示例包含一个我们还没提到的函数:pcap_lookupnet(),向这个函数提供设备接口名,它将返回其IP和网络掩码,这是很基本的,因为我们需要知道网络掩码以便应用过滤器。此函数在此文最后的miscellaneous一节里还有描述。 据我的经验,这个过滤器在所有的操作系统下都不会工作。在我的测试环境里,我发现OpenBSD 2.9默认内核支持这种过滤器,但FreeBSD 4.3默认内核则不支持。你的情况可能会有变化。 实际的嗅探 到此为止,我们已经学习了如何定义一个设备,让它准备嗅探,还有应用过滤器使我们嗅谈到什么或者不嗅探到什么。现在到了真正去捕获一些数据包的时候了。有两种手段捕获包。我们可以一次只捕获一个包,也可以进入一个循环,等捕获到多个包再进行处理。我们将先看看怎样去捕获单个包,然后再看看使用循环的方法。为此,我们使用函数pcap_next()。 Pcap_next()的原型及其简单: u_char *pcap_next(pcap_t *p, struct pcap_pkthdr *h) 第一个参数是会话句柄,第二个参数是指向一个包括了当前数据包总体信息(被捕获时的时间,包的长度,其被指定的部分长度)的结构体的指针(在这里只有一个片断,只作为一个示例)。Pcap_next()返回一个u_char指针给被这个结构体描述的包。我们将稍后讨论这种实际读取包本身的手段。 这里有一个演示怎样使用pcap_next()来嗅探一个包的例子: #include 3 #include pcap_t *handle; /* 会话句柄 */ char *dev; /* 执行嗅探的设备 */ char errbuf[PCAP_ERRBUF_SIZE]; /* 存储错误信息的字符串 */ struct bpf_program filter; /* 已经编译好的过滤器 */ char filter_app[] = \"port 23\"; /* 过滤表达式 */ bpf_u_int32 mask; /* 所在网络的掩码 */ bpf_u_int32 net; /* 主机的IP地址 */ struct pcap_pkthdr header; /* 由pcap.h定义 */ const u_char *packet; /* 实际的包 */ /* Define the device */ dev = pcap_lookupdev(errbuf); pcap_lookupnet(dev, &net, &mask, errbuf); /* 探查设备属性 */ handle = pcap_open_live(dev, BUFSIZ, 1, 0, errbuf); /* 以混杂模式打开会话 */ pcap_compile(handle, &filter, filter_app, 0, net); /* 编译并应用过滤器 */ pcap_setfilter(handle, &filter); packet = pcap_next(handle, &header); /* 截获一个包 */ printf(\"Jacked a packet with length of [%d]\打印它的长度 */ pcap_close(handle); /* 关闭会话 */ return(0); } 这个程序嗅探被pcap_lookupdev()返回的设备并将它置为混杂模式。它发现第一个包经过端口23(telnet)并且告诉用户此包的大小(以字 节为单位)。这个程序又包含了一个新的调用pcap_close(),我们将在后面讨论(尽管它的名字就足够证明它自己的作用)。 我们可以使用的另一种手段则要复杂的多,并且可能也更为有用。很少有(如果有的话)嗅探器真正的使用pcap_next()。通常,它们使用pcap_loop()或者 pcap_dispatch()(它就是用了pcap_loop())。为了理解这两个函数的用法,你必须理解回调函数的思想。 回调函数并不是什么新东西,它在许多API里面非常普遍。回调函数的概念极其简单。设想我有一个程序正等待某种排序的事件。为了达到这个例子的目的,让我们假象我的程序想让用户在键盘上按下一个键,每当他们按下了一个键,我就想调用一个作相应处理的函数。我所用的函数就是一个回调函数。用户每按一个键一次,我的程序就调用回调函数一次。回调函数在应用在pcap里,取代当用户按下键时被调用的函数的是当pcap嗅探到一个数据包时所调用的函数。可以定义它们的回调函数的两个函数就是pcap_loop()和pcap_dispatch()。此二者在它们的回调函数的使用上非常的相似。它们都是每当捕获到一个符合我们过滤器的包时调用器回调函数(当然是存在一个过滤器时,如果不存在则所有被嗅探到的包都被送到会调函数处理)。 Pcap_loop()的原型如下: int pcap_loop(pcap_t *p, int cnt, pcap_handler callback, u_char *user) 第一个参数是会话句柄,接下来是一个整型,它告诉pcap_loop()在返回前应捕获多少个数据包(若为负值则表示应该一直工作直至错误发生)。第三个参数是回调函数的名称(正像其标识符所指,无括号)。最后一个参数在有些应用里有用,但更多时候则置为NULL。假设我们有我们自己的想送往回调函数的参数,另外还有pcap_loop()发送的参数,这就需要用 4 到它。很明显,必须是一个u_char类型的指针以确保结果正确;正像我们稍后见到的, pcap使用了很有意思的方法以u_char指针的形势传递信息。在我们展示了一个pcap是怎样做的例子之后就很容易去做了。若是还不行就参考你的本地的C引用文本,作为一个指针的解释那就超出了本文的范围。 Pcap_dispatch()的用法几乎相同。唯一不同的是它们如何处理超时(还记得在调用pcap_open_live()时怎样设置超时吗?这就是它起作用的地方)。Pcap_loop()忽略超时而pcap_dispatch()则不。关于它们之间区别的更深入的讨论请参见pcap的手册页。 在提供使用pcap_loop()的示例之前,我们必须检查我们的回调函数的格式。我们不能武断的定义回调函数的原型,否则pcap_loop()将会不知道如何去使用它。因此我们使用这样的格式作为我们的回调函数的原型: void got_packet(u_char *args, const struct pcap_pkthdr *header, const u_char *packet); 让我们更细致的考察它。首先,你会注意到该函数返回void类型,这是符合逻辑的,因为pcap_loop()不知道如何去处理一个回调返回值。第一个参数相应于pcap_loop()的最后一个参数。每当回调函数被调用时,无论最后一个参数传给pcap_loop()什么值,这个值都会传给我们回调函数的第一个参数。第二个参数是pcap头文件定义的,它包括数据包被嗅探的时间、大小等信息。结构体pcap_pkhdr在pcap.h中定义如下: struct pcap_pkthdr { struct timeval ts; /* 时间戳 */ bpf_u_int32 caplen; /* 已捕获部分的长度 */ bpf_u_int32 len; /* 该包的脱机长度 */ }; 这些量都相当明了。最后一个参数在它们中是最有意思的,也最让pcap程序新手感到迷惑。这又是一个u_char指针,它包含了被pcap_loop()嗅探到的所有包。 但是你怎样使用这个我们在原型里称为packet的变量呢?一个数据包包含许多属性,因此你可以想象它不只是一个字符串,而实质上是一个结构体的集合(比如,一个TCP/IP包会有一个以太网的头部,一个IP头部,一个TCP头部,还有此包的有效载荷)。这个u_char就是这些结构体的串联版本。为了使用它,我们必须作一些有趣的匹配工作。 首先,在匹配它们之前必须定义这些实际的结构体。下面就是我用来描述一个通过以太网的TCP/IP包的结构体的定义。我使用的所有这些定义都是直接从POSIX库中提取的。通常,我只简单的使用那些库中的定义即可,但据我的经验不同平台的库之间有轻微的差别,这使得它实现起来变得混乱。因此,为达到示例的目的,我就避免那些混乱而简单的复制这些有关的结构体。所有这些都能在你的本地unix系统中的include/netinet中找到。下面就是这些结构体: /* 以太网帧头部 */ struct sniff_ethernet { u_char ether_dhost[ETHER_ADDR_LEN]; /* 目的主机的地址 */ u_char ether_shost[ETHER_ADDR_LEN]; /* 源主机的地址 */ u_short ether_type; /* IP? ARP? RARP? etc */ }; /* IP数据包的头部 */ struct sniff_ip { #if BYTE_ORDER == LITTLE_ENDIAN u_int ip_hl:4, /* 头度 */ ip_v:4; /* 版本号 */ #if BYTE_ORDER == BIG_ENDIAN 5 u_int ip_v:4, /* 版本号 */ ip_hl:4; /* 头度 */ #endif #endif /* not _IP_VHL */ u_char ip_tos; /* 服务的类型 */ u_short ip_len; /* 总长度 */ u_short ip_id; /*包标志号 */ u_short ip_off; /* 碎片偏移 */ #define IP_RF 0x8000 /* 保留的碎片标志 */ #define IP_DF 0x4000 /* dont fragment flag */ #define IP_MF 0x2000 /* 多碎片标志*/ #define IP_OFFMASK 0x1fff /*分段位 */ u_char ip_ttl; /* 数据包的生存时间 */ u_char ip_p; /* 所使用的协议 */ u_short ip_sum; /* 校验和 */ struct in_addr ip_src,ip_dst; /* 源地址、目的地址*/ }; /* TCP 数据包的头部 */ struct sniff_tcp { u_short th_sport; /* 源端口 */ u_short th_dport; /* 目的端口 */ tcp_seq th_seq; /* 包序号 */ tcp_seq th_ack; /* 确认序号 */ #if BYTE_ORDER == LITTLE_ENDIAN u_int th_x2:4, /* 还没有用到 */ th_off:4; /* 数据偏移 */ #endif #if BYTE_ORDER == BIG_ENDIAN u_int th_off:4, /* 数据偏移*/ th_x2:4; /*还没有用到 */ #endif u_char th_flags; #define TH_FIN 0x01 #define TH_SYN 0x02 #define TH_RST 0x04 #define TH_PUSH 0x08 #define TH_ACK 0x10 #define TH_URG 0x20 #define TH_ECE 0x40 #define TH_CWR 0x80 #define TH_FLAGS (TH_FIN|TH_SYN|TH_RST|TH_ACK|TH_URG|TH_ECE|TH_CWR) u_short th_win; /* TCP滑动窗口 */ u_short th_sum; /* 头部校验和 */ u_short th_urp; /* 紧急服务位 */ 6 }; 注:在Slackware Linux 8(内核版本2.2.19)上我发现使用以上结构体的代码将不能通过编译。后来证明问题在于include/fearures.h,它只实现了一个 POSIX接口,除非定义BSD_SOURCE。如果它没有被定义,我就只能使用一个不同的结构体去定义TCP头部。使它们工作在FreeBSD或 OpenBSD系统上的更为通用的解决方法如下: #define _BSD_SOURCE 1 事先要包含你自己的所有头文件。这将确保正常使用BSD风格的API。如果不想这样做,那你可以改变TCP头结构(点此链接即可,内含注释)。 那么所有这些与pcap还有神秘的u_char是怎么关联的呢?看,幸运的是pcap嗅探数据包时正是使用的这些结构。接下来,它简单的创建一个 u_char字符串并且将这些结构体填入。那么我们怎样才能区分它们呢?准备好见证指针最实用的好处之一吧(在此,我可要刺激刺激那些坚持说指针无用的C 程序新手了)。 我们再一次假定要对以太网上的TCP/IP包进行处理。同样的手段可以应用于任何数据包,唯一的区别是你实际所使用的结构体的类型。让我们从声明分解u_char包的变量开始: const struct sniff_ethernet *ethernet; /* 以太网帧头部*/ const struct sniff_ip *ip; /* IP包头部 */ const struct sniff_tcp *tcp; /* TCP包头部 */ const char *payload; /* 数据包的有效载荷*/ /*为了让它的可读性好,我们计算每个结构体中的变量大小*/ int size_ethernet = sizeof(struct sniff_ethernet); int size_ip = sizeof(struct sniff_ip); int size_tcp = sizeof(struct sniff_tcp); 现在我们开始让人感到有些神秘的匹配: ethernet = (struct sniff_ethernet*)(packet); ip = (struct sniff_ip*)(packet + size_ethernet); tcp = (struct sniff_tcp*)(packet + size_ethernet + size_ip); payload = (u_char *)(packet + size_ethernet + size_ip + size_tcp); 此处如何工作?考虑u_char在内存中的层次。基本的,当pcap将这些结构体填入u_char的时候是将这些数据存入一个字符串中,那个字符串将被送入我们的会调函数中。反向转换是这样的,不考虑这些结构中的值,它们的大小将是一致的。例如在我的平台上,一个sniff_ethernet结构体的大小是14字节。一个sniff_ip结构体是20字节,一个sniff_tcp结构体也是20字节。 u_char指针正是包含了内存地址的一个变量,这也是指针的实质,它指向内存的一个区域。简单而言,我们说指针指向的地址为x,如果三个结构体恰好线性排列,第一个(sniff_ethernet)被装载到内存地址的x处则我们很容易的发现其他结构体的地址,让我们以表格显示之: Variable Location (in bytes) sniff_ethernet X sniff_ip X + 14 sniff_tcp X + 14 + 20 payload X + 14 + 20 + 20 结构体sniff_ethernet正好在x处,紧接着它的sniff_ip则位于x加上它本身占用的空间(此例为14字节),依此类推可得全部地址。 注意:你没有假定你的变量也是同样大小是很重要的。你应该总是使用sizeof()来确保尺寸的正确。这是因为这些结构体中的每个成员在不同平台下可以有不同的尺寸。 7 到现在,我们已经知道了怎样设置回调函数,调用它,弄清被嗅探到的数据包的属性。你可能正期待着写出一个可用的包嗅探器。因为代码的长度关系,我不想列在这篇文章里。你可以点击这里下载并测试它。 结束语 到此为止,你应该可以写出一个基于pcap的包嗅探器了。你已经学习了基本的概念:打开一个pcap会话,有关它的全体属性,嗅探数据包,使用过滤器,使用回调函数,等等。现在是进行数据包嗅探的时候了。 libpcap使用总结! libpcap 是一个开发sniffer的工具包。pcap:packet capture! libpcap的数据类型定义: struct pcap_addr:网卡地址描述 { pcap_addr * next; sockaddr * addr; sockaddr * netmask; sockaddr *broadaddr; sockaddr *dstaddr; }; pcap_addr * next; 如果非空,指向链表中一个元素的指针;空表示链表中的最后一个元素。 sockaddr * addr; 指向包含一个地址的sockaddr的结构的指针。 sockaddr * netmask; 如果非空,指向包含相对于addr指向的地址的一个网络掩码的结构。 sockaddr * broadaddr; 如果非空,指向包含相对于addr指向的地址的一个广播地址,如果网络不支持广播可能为空。 sockaddr * dstaddr; 如果非空,指向一个相对于addr指向的源地址的目的地址,如果网络不支持点对点通讯,则为空。 struct pcap_file_header { bpf_u_int32 magic; u_short version_major; u_short version_minor; bpf_int32 thiszone; /* gmt to local correction */ bpf_u_int32 sigfigs; /* accuracy of timestamps */ bpf_u_int32 snaplen; /* max length saved portion of each pkt */ bpf_u_int32 linktype; /* data link type (LINKTYPE_*) */ }; bpf_u_int32 magic;/*magic是32位数值,代表了存储类型*/ u_short version_major;/* Libpcap的主版本号*/ 8 u_shart version_minor;/* Libpcap的从版本号*/ bpf_u_int32 sigfigs;/* 时间戳描述*/ bpf_u_int32 snaplen;/* 保存的每个pkt的分片号的最大值*/ bpf_u_int32 linktype;/* 数据链的类型*/ 细节说明: libpcap dump文件头; libpcap dump文件中的第一个记录包含了一些标志的保存值,这些标志在打印阶段用到。这儿的很多域都是32位的int,所以compilers不用进行转化;这些文件需要具有跨层次的可交换性。 无论如何不要改变结构的层次(包括仅仅改变这个结构中域的长度); struct pcap_if { /*网卡数据链的一个元素*/ struct pcap_if *next; char *name; /* name to hand to \"pcap_open_live()\" */ char *description; /* textual description of interface, or NULL */ struct pcap_addr *addresses; u_int flags; /* PCAP_IF_ interface flags */ }; pcap_if *next; 如果非空,指向链的下一个元素。如果为空是链的最后一个元素。 char * name; 指向一个字符串,该字符串是传给pcap_open_live()函数的设备名; char * description; 如果非空,指向一个对设备的人性化的描述字符串。 pcap_addr *addresses; 指向网卡地址链中的第一个元素。 u_int flags; PCAP_IF_ 网卡的标志。现在唯一可用的标识是PCAP_IF_LOOKBACK,它被用来标识网卡是不是lookback网卡。 struct pcap_pkthdr { /*dump 文件中的数据包头*/ struct timeval ts; /* time stamp */ bpf_u_int32 caplen; /* length of portion present */ bpf_u_int32 len; /* length this packet (off wire) */ }; timeval ts; 数据报时间戳; bpf_u_int32 caplen; 当前分片的长度; dpf_u_int32 len; 这个数据报的长度; 细节描述: 在dump文件中的每个数据报都有这样一个报头。它用来处理不同数据报网卡的不同报头问题。 struct pcap_stat { /*用来保存网卡静态变量的结构*/ u_int ps_recv; /* number of packets received */ u_int ps_drop; /* number of packets dropped */ u_int ps_ifdrop; /* drops by interface XXX not yet supported */ }; u_int ps_recv; 接受数据报的数目; u_int ps_drop; 被驱动程序丢弃的数据报的数目; 9 u_int ps_ifdrop; 被网卡丢弃的数据报的数目; lipcap的声明: #define PCAP_VERSION_MAJOR 2 libpcap dump文件的主版本号; #define PCAP_VERSION_MINOR 4 libpcap dump文件的从版本号; #define PCAP_ERRBUF_SIZE 256 用来存放libpcap出错信息的缓冲区的大小; #define PCAP_IF_LOOPBACK 0x00000001 网卡是回环网卡; #define MODE_CAPT 0 抓报模式,在调用pcap_setmode()时使用; #define MODE_STAT 1 静态模式,在调用pcap_setmode()时使用; libpcap的类型定义: typedef int bpf_int32 32bit 的整形; typedef u_int bpf_u_int32 32bit 的无类型整形; typedef pcap pcap_t Descriptor of an open capture instance(一个打开的捕获实例的描述符?)这个结构对用户是不透明的。 typedef pcap_dumper pcap_dumper_t libpcap保存文件的描述符。 typedef pcap_if pcap_if_t 网卡链表的一个元素; typedef pcap_addr pcap_addr_t 网卡地址的表示; libpcap函数描述: char *pcap_lookupdev(char * errbuf); 描述:这个函数用于获取一个合适的网卡描述,以供pcap_open_liver函数和pcap_lookupnet函数使用。如果找不到网卡或者所有网卡为 off,则返回null。如果一个系统中有多个网卡,那么该函数返回找到的第一个on的网卡。最后才是回环接口。回环网卡一直被忽略; 参数: char * errbuf 存放pcap_lookupdev函数的出错信息,只有在pcap_lookup失败是才有值。 返回值: 如果函数执行成功,则返回一个用于描述系统上的一个网卡的描述符的指针。如果失败,返回null,errbuf中存放出错信息。 int pcap_lookupnet(char * device, bpf_u_int32 * netp, bpf_u_int32 * maskp,char * errbuf); 描述:该函数用于监测网卡所在网络的网络地址和子网掩码。 参数: char *devic:网卡的描述符指针,由pcap_looupdev函数获取; bpf_u_int32 *netp:存放网络地址; bpf_u_int32 *maskp:存放子网掩码; char * errbuf: 存放出错信息; 返回值:如果函数执行成功,则返回值为0,否则返回值为-1,并在errbuf中存放出错信息。 pcap_t *pcap_open_live(char * device, int snaplen,int promisc, int to_ms, char * ebuf); 描述:该函数用于打开网卡用于捕获数据报。单词live的意思就是表示一个运行的网卡(相对于offline而言)被打开了,如同一个保存有被抓数据报的文件被打开一样。在捕获数据报之前这个函数必须被执行。所有的其他的用于处理数据报捕获的函数用到的捕获数据报的描述符由该函数产生。查看 pcap_open_offlin()函数的定义,了解如何打开一个预先保存的包含数据报的文件的细节。 参数: 10 char *device:网卡的描述符指针,由pcap_looupdev函数获取; int snaplen:规定捕获的每个数据报的最大字节数; int promisc:1为混杂模式;0为非混杂模式; int to_ms:规定读超时的微秒(milliseconds)数; char *ebuf:存放错误信息,只有在pcap_open_live失败时才被设置; 返回值:如果函数成功执行,则返回一个指向数据报捕获的指针;如果错误,返回null,ebuf存放出错信息; int pcap_compile(pcap_t * p, struct bpf_ program *fp, char * str,int optimize, bpf_u_int32 netmask); 描述:该函数用于将str指定的规则整合到fp过滤程序中去,并生成过滤程序入口地址,用于过滤选择期望的数据报; 参数: pcap_t *p:pcap_open_live返回的数据报捕获的指针; struct bpf_program *fp:指向一个子函数用于过滤,在pcap_compile()函数中被赋值; char *str:该字符串规定过滤规则; int optimize:规定了在结果代码上的选择是否被执行; bpf_u_int32 netmask:该网卡的子网掩码,可以通过pcap_lookupnet()获取; 返回值: 如果成功执行,返回0,否则返回-1; int pcap_loop(pcap_t * p, int cnt, pcap_handler callback,u_char * user); 描述: 该函数用于读取和处理数据报。既可以用来处理事先捕获的保存在文件中的数据报,也可以用来处理实时捕获的数据报; 这个函数类似于pcap_dispatch函数,除了它继续读取数据报直至完成cnt个报的处理,或者文件处理完(在offline情况下),或者有错误发生为止。它不会在实时读超时时返回(而如果为pcap_open_live()函数指定了一个非零值的超时设置,然后调用 pcap_dispatch()函数,则当超时发生时pcap_dispatch()函数会返回。) 注意第三个参数,callback是pcap_handler类型的变量。这是一个用户提供的有着三个参数的子函数。定义为: void user_routine(u_char *user, struct pcap_pkthdr *phrd, u_char *pdata) 这三个参数中,user,是传递给pcap_dispatch()的那个参数;phdr,是个pcap_pkthdr类型的指针,是savefile中的数据报的头指针,pdata,指向数据报数据;这个函数允许用户定义子集的数据报过滤程序; 参数: pcap_t * pcap_open_live返回的数据报捕获的指针; int cnt:规定了函数返回前应处理的数据报数目; pcap_handler callback:指向一个用户自定义的函数,在处理每个报后自动调用该函数进行再处理; u_char *user:该指针用于传递给callback.(不知道有什么用?) 返回值: 如果函数成功执行(包括读文件时读到EOF),则返回0.否则返回-1,那么错误信息将由函数pcap_geterr或pcap_perror给出; 11 libpcap使用举例(1) 我们曾经提供过< 今天讨论的不是效率,而是可能的移植性要求。没办法,第一次使用libpcap库,举例能深入到什么地步,不知道。如果你也是第一次用这个库,跟我来,第N次使用?那还是忙你的去吧,别来看这篇无聊的灌水,:-P。我无聊是因为有程序要广泛可移植,你无聊是为什么。 char * pcap_lookupdev ( char * errbuf ); 该函数返回一个网络设备接口名,类似libnet_select_device(),对于Linux就是\"eth0\"一类的名字。pcap_open_live()、pcap_lookupnet()等函数将用到这个网络设备接口名。失败时返回NULL,errbuf包含了失败原因。errbuf一般定义如下: /usr/include/pcap.h #define PCAP_ERRBUF_SIZE 256 char errbuf[ PCAP_ERRBUF_SIZE ]; pcap_t * pcap_open_live ( char * device, int snaplen, int promisc, int to_ms, char * errbuf ); 该函数用于获取一个抽象的包捕捉句柄,后续很多libpcap函数将使用该句柄,类似文件操作函数频繁使用文件句柄。device指定网络接口设备名,比如\"eth0。snaplen指定单包最大捕捉字节数,为了保证包捕捉不至于太低效率,snaplen尽量适中,以恰能获得所需协议层数据为准。promisc指定网络接口是否进入混杂模式,注意即使该参数为false(0),网络接口仍然有可能因为其他原因处在混杂模式。to_ms指定毫秒级读超时,man手册上并没有指明什么值意味着永不超时,测试下来的结论,0可能代表永不超时。如果调用失败返回NULL,errbuf包含失败原因。 -------------------------------------------------------------------------- /usr/include/pcap.h typedef struct pcap pcap_t; pcap-int.h里定义了struct pcap {} struct pcap { int fd; int snapshot; int linktype; int tzoff; /* timezone offset */ int offset; /* offset for proper alignment*/ struct pcap_sf sf; struct pcap_md md; int fsize; /* Read buffer */ u_char * buffer; u_char * bp; int cc; 12 u_char * pkt; /* Place holder for pcap_next()*/ struct bpf_program fcode; /* Placeholder for filter code if bpf not in kernel. */ char errbuf[PCAP_ERRBUF_SIZE]; }; -------------------------------------------------------------------------- int pcap_lookupnet ( char * device, bpf_u_int32 * netp, bpf_u_int32 * maskp, char * errbuf ); 该函数用于获取指定网络接口的IP地址、子网掩码。不要被netp的名字所迷惑,它对应的就是IP地址,maskp对应子网掩码。 /usr/include/pcap.h typedef u_int bpf_u_int32; 显然简单理解成32-bit即可。如果调用失败则返回-1,errbuf包含失败原因。 int pcap_compile ( pcap_t * p, struct bpf_program * fp, char * str, int optimize, bpf_u_int32 netmask ); 该函数用于解析过滤规则串,填写bpf_program结构。str指向过滤规则串,格式参看tcpdump的man手册,比如: tcpdump -x -vv -n -t ip proto \\\cp and dst 192.168.8.90 and tcp[13] \\& 2 = 2 这条过滤规则将捕捉所有携带SYN标志的到192.168.8.90的TCP报文。过滤规则串可以是空串(\"\"),表示抓取所有过路的报文。 optimize为1表示对过滤规则进行优化处理。netmask指定子网掩码,一般从pcap_lookupnet()调用中获取。返回值小于零表示调用失败。 这个函数可能比较难于理解,涉及的概念源自BPF,Linux系统没有这种概念,但是 libpcap采用pcap_compile()和pcap_setfilter()结合的办法屏蔽了各种链路层支持 的不同,无论是SOCK_PACKET、DLPI。曾在华中Security版上写过一篇<<内核包捕获过滤机制介绍>>,参看该文加强理解。 -------------------------------------------------------------------------- # tcpdump -d ip proto \\\cp and dst 192.168.8.90 and tcp[13] \\& 2 = 2 (000) ldh [-4096] (001) jeq #0x800 jt 2 jf 13 (002) ldb [9] (003) jeq #0x6 jt 4 jf 13 (004) ld [16] (005) jeq #0xc0a8085a jt 6 jf 13 (006) ldh [6] (007) jset #0x1fff jt 13 jf 8 (008) ldxb 4*([0]&0xf) (009) ldb [x + 13] (010) and #0x2 (011) jeq #0x2 jt 12 jf 13 (012) ret #65535 (013) ret #0 # 13 /usr/include/net/bpf.h /* Structure for BIOCSETF. */ struct bpf_program { u_int bf_len; struct bpf_insn * bf_insns; }; /* * The instruction data structure. */ struct bpf_insn { u_short code; u_char jt; u_char jf; bpf_int32 k; }; /* * Macros for insn array initializers. */ #define BPF_STMT(code, k) { (u_short)(code), 0, 0, k } #define BPF_JUMP(code, k, jt, jf) { (u_short)(code), jt, jf, k } -------------------------------------------------------------------------- int pcap_setfilter ( pcap_t * p, struct bpf_program * fp ); 该函数用于设置pcap_compile()解析完毕的过滤规则,如果你足够聪明(愚公?),完全可以自己提供过滤规则,无须pcap_compile()介入,就象你写Password Sniffer For I386/FreeBSD时常做的那样。成功返回0,失败返回-1。 int pcap_dispatch ( pcap_t * p, int cnt, pcap_handler callback, u_char * user ); 该函数用于捕捉报文、分发报文到预先指定好的处理函数(回调函数)。pcap_dispatch()接收够cnt个报文便返回,如果cnt为-1意味着所有报文集中在一个缓冲区中。如果cnt为0,仅当发生错误、读取到EOF或者读超时到了(pcap_open_live中指定)才停止捕捉报文并返回。callback指定如下类型的回调函数,用于处理pcap_dispatch()所捕获的报文: typedef void ( *pcap_handler ) ( u_char *, const struct pcap_pkthdr *, const u_char * ); pcap_dispatch()返回捕捉到的报文个数,如果在读取静态文件(以前包捕捉过程中存储下来的)时碰到EOF则返回0。返回-1表示发生错误,此时可以用pcap_perror()、pcap_geterr()显示错误信息。 下面来看看那个回调函数,总共有三个参数,第一个形参来自pcap_dispatch()的第三个形参,一般我们自己的包捕捉程序不需要提供它,总是为NULL。第二个形参指向pcap_pkthdr结构,该结构位于真正的物理帧前面,用于消除不同链路层支持的差异。最后的形参指向所捕获报文的物理帧。 14 -------------------------------------------------------------------------- /usr/include/pcap.h /* * Each packet in the dump file is prepended with this generic header. * This gets around the problem of different headers for different * packet interfaces. */ struct pcap_pkthdr { struct timeval ts; /* time stamp */ bpf_u_int32 caplen; /* length of portion present */ bpf_u_int32 len; /* length this packet (off wire) */ }; /usr/include/net/bpf.h /* * Structure prepended to each packet. */ struct bpf_hdr { struct timeval bh_tstamp; /* time stamp */ bpf_u_int32 bh_caplen; /* length of captured portion */ bpf_u_int32 bh_datalen; /* original length of packet */ u_short bh_hdrlen; /* length of bpf header (this struct plus alignment padding) */ }; /* * Because the structure above is not a multiple of 4 bytes, some compilers * will insist on inserting padding; hence, sizeof(struct bpf_hdr) won't work. * Only the kernel needs to know about it; applications use bh_hdrlen. */ #ifdef KERNEL #define SIZEOF_BPF_HDR 18 #endif -------------------------------------------------------------------------- void pcap_close ( pcap_t * p ); 该函数用于关闭pcap_open_live()获取的包捕捉句柄,释放相关资源。 void pcap_perror ( pcap_t * p, char * prefix ); 第一形参来自pcap_open_live(),第二行参的作用类似perror()的形参,指定错误信息的前缀,与perror()一样,结尾自动输出一个换行。 pcap_perror( p, \"pcap_compile\" )的输出类似这个效果: 15 pcap_compile: unknown ip proto ... pcap_perror并不自动exit(),与perror()一样,如果需要,应该显式调用exit()。 介绍到这里,已经可以写简单的sniffer。出于完整演示目的,提供这样一个sample code。请勿询问任何关于该代码的问题,烦了。 -------------------------------------------------------------------------- /* * File : sniffer program for I386/Linux using libpcap * Version: 0.01 aleph * Author : Anonymous ( Don't ask anything about this program, please. ) * Complie: gcc -O3 -o pcap pcap_sniffer.c -lpcap `libnet-config --defines --cflags` -Wall * : strip pcap * Usage : ./pcap -h * Date : 2000-12-15 16:35 */ /******************************************************************* * * * Head File * * * *******************************************************************/ #include #include /******************************************************************* * * * Macro * * * *******************************************************************/ #define SUCCESS 0 #define FAILURE -1 typedef void Sigfunc ( int ); /* for signal handlers */ /******************************************************************* * * * Static Global Var * * * 16 *******************************************************************/ static pcap_t * pcap_fd = NULL; /* 抽象的包捕捉句柄 */ /******************************************************************* * * * Function Prototype * * * *******************************************************************/ static void Atexit ( void ( * func ) ( void ) ); static void bpf_dump ( struct bpf_program * p, int option ); char *bpf_image ( struct bpf_insn * p, int n ); static void outputBinary ( const u_char * byteArray, const size_t byteArrayLen ); static void pcap_callback ( u_char * none, const struct pcap_pkthdr * pcap_head, const u_char * packet ); static pcap_t * pcap_init ( char * dev, char * filter, int snaplen, int timeout, int dumplevel ); static void pcap_read ( pcap_t * p ); static void sig_end ( int signo ); Sigfunc * signal ( int signo, Sigfunc * func ); static Sigfunc * Signal ( int signo, Sigfunc * func ); /* for our signal() function */ static void terminate ( void ); static void usage ( char * arg ); /*----------------------------------------------------------------------*/ static void Atexit ( void ( * func ) ( void ) ) { if ( atexit( func ) != 0 ) { exit( FAILURE ); } return; } /* end of Atexit */ static void bpf_dump ( struct bpf_program * p, int option ) { struct bpf_insn * insn; int i; int n = p->bf_len; insn = p->bf_insns; if ( option > 2 ) { 17 fprintf( stderr, \"%d\\n\ for ( i = 0; i < n; ++insn, ++i ) { fprintf( stderr, \"%u %u %u %u\\n\ insn->jt, insn->jf, insn->k ); } return; } if ( option > 1 ) { for ( i = 0; i < n; ++insn, ++i ) { fprintf( stderr, \"{ 0x%x, %d, %d, 0x%08x },\\n\ insn->code, insn->jt, insn->jf, insn->k ); } return; } for ( i = 0; i < n; ++insn, ++i ) { puts( bpf_image( insn, i ) ); } } /* end of bpf_dump */ char * bpf_image ( struct bpf_insn * p, int n ) { int v; char * fmt; char * op; static char image[256]; char operand[]; v = p->k; switch ( p->code ) { default: op = \"unimp\"; fmt = \"0x%x\"; v = p->code; break; case BPF_RET|BPF_K: op = \"ret\"; fmt = \"#%d\"; break; case BPF_RET|BPF_A: op = \"ret\"; 18 fmt = \"\"; break; case BPF_LD|BPF_W|BPF_ABS: op = \"ld\"; fmt = \"[%d]\"; break; case BPF_LD|BPF_H|BPF_ABS: op = \"ldh\"; fmt = \"[%d]\"; break; case BPF_LD|BPF_B|BPF_ABS: op = \"ldb\"; fmt = \"[%d]\"; break; case BPF_LD|BPF_W|BPF_LEN: op = \"ld\"; fmt = \"#pktlen\"; break; case BPF_LD|BPF_W|BPF_IND: op = \"ld\"; fmt = \"[x + %d]\"; break; case BPF_LD|BPF_H|BPF_IND: op = \"ldh\"; fmt = \"[x + %d]\"; break; case BPF_LD|BPF_B|BPF_IND: op = \"ldb\"; fmt = \"[x + %d]\"; break; case BPF_LD|BPF_IMM: op = \"ld\"; fmt = \"#0x%x\"; break; case BPF_LDX|BPF_IMM: op = \"ldx\"; fmt = \"#0x%x\"; break; case BPF_LDX|BPF_MSH|BPF_B: op = \"ldxb\"; fmt = \"4*([%d]&0xf)\"; break; case BPF_LD|BPF_MEM: op = \"ld\"; 19 fmt = \"M[%d]\"; break; case BPF_LDX|BPF_MEM: op = \"ldx\"; fmt = \"M[%d]\"; break; case BPF_ST: op = \"st\"; fmt = \"M[%d]\"; break; case BPF_STX: op = \"stx\"; fmt = \"M[%d]\"; break; case BPF_JMP|BPF_JA: op = \"ja\"; fmt = \"%d\"; v = n + 1 + p->k; break; case BPF_JMP|BPF_JGT|BPF_K: op = \"jgt\"; fmt = \"#0x%x\"; break; case BPF_JMP|BPF_JGE|BPF_K: op = \"jge\"; fmt = \"#0x%x\"; break; case BPF_JMP|BPF_JEQ|BPF_K: op = \"jeq\"; fmt = \"#0x%x\"; break; case BPF_JMP|BPF_JSET|BPF_K: op = \"jset\"; fmt = \"#0x%x\"; break; case BPF_JMP|BPF_JGT|BPF_X: op = \"jgt\"; fmt = \"x\"; break; case BPF_JMP|BPF_JGE|BPF_X: op = \"jge\"; fmt = \"x\"; break; case BPF_JMP|BPF_JEQ|BPF_X: 20 op = \"jeq\"; fmt = \"x\"; break; case BPF_JMP|BPF_JSET|BPF_X: op = \"jset\"; fmt = \"x\"; break; case BPF_ALU|BPF_ADD|BPF_X: op = \"add\"; fmt = \"x\"; break; case BPF_ALU|BPF_SUB|BPF_X: op = \"sub\"; fmt = \"x\"; break; case BPF_ALU|BPF_MUL|BPF_X: op = \"mul\"; fmt = \"x\"; break; case BPF_ALU|BPF_DIV|BPF_X: op = \"div\"; fmt = \"x\"; break; case BPF_ALU|BPF_AND|BPF_X: op = \"and\"; fmt = \"x\"; break; case BPF_ALU|BPF_OR|BPF_X: op = \"or\"; fmt = \"x\"; break; case BPF_ALU|BPF_LSH|BPF_X: op = \"lsh\"; fmt = \"x\"; break; case BPF_ALU|BPF_RSH|BPF_X: op = \"rsh\"; fmt = \"x\"; break; case BPF_ALU|BPF_ADD|BPF_K: op = \"add\"; fmt = \"#%d\"; break; case BPF_ALU|BPF_SUB|BPF_K: 21 op = \"sub\"; fmt = \"#%d\"; break; case BPF_ALU|BPF_MUL|BPF_K: op = \"mul\"; fmt = \"#%d\"; break; case BPF_ALU|BPF_DIV|BPF_K: op = \"div\"; fmt = \"#%d\"; break; case BPF_ALU|BPF_AND|BPF_K: op = \"and\"; fmt = \"#0x%x\"; break; case BPF_ALU|BPF_OR|BPF_K: op = \"or\"; fmt = \"#0x%x\"; break; case BPF_ALU|BPF_LSH|BPF_K: op = \"lsh\"; fmt = \"#%d\"; break; case BPF_ALU|BPF_RSH|BPF_K: op = \"rsh\"; fmt = \"#%d\"; break; case BPF_ALU|BPF_NEG: op = \"neg\"; fmt = \"\"; break; case BPF_MISC|BPF_TAX: op = \"tax\"; fmt = \"\"; break; case BPF_MISC|BPF_TXA: op = \"txa\"; fmt = \"\"; break; } /* end of switch */ ( void )sprintf( operand, fmt, v ); ( void )sprintf( image, ( BPF_CLASS( p->code ) == BPF_JMP && BPF_OP( p->code ) != BPF_JA ) ? \"(%03d) %-8s %-16s jt %d\jf %d\" : \"(%03d) %-8s %s\1 + p->jf ); 22 return image; } /* end of bpf_image */ static void outputBinary ( const u_char * byteArray, const size_t byteArrayLen ) { u_long offset; int i, j, k; fprintf( stderr, \"byteArray [ %lu bytes ] ----> \\n\ if ( byteArrayLen <= 0 ) { return; } i = 0; offset = 0; for ( k = byteArrayLen / 16; k > 0; k--, offset += 16 ) { fprintf( stderr, \"%08X \ for ( j = 0; j < 16; j++, i++ ) { if ( j == 8 ) { fprintf( stderr, \"-%02X\ } else { fprintf( stderr, \" %02X\ } } fprintf( stderr, \" \" ); i -= 16; for ( j = 0; j < 16; j++, i++ ) { /* if ( isprint( (int)byteArray[i] ) ) */ if ( ( byteArray[i] >= ' ' ) && ( byteArray[i] <= 255 ) ) { fprintf( stderr, \"%c\ } else { fprintf( stderr, \".\" ); } } fprintf( stderr, \"\\n\" ); 23 } /* end of for */ k = byteArrayLen - i; if ( k <= 0 ) { return; } fprintf( stderr, \"%08X \ for ( j = 0 ; j < k; j++, i++ ) { if ( j == 8 ) { fprintf( stderr, \"-%02X\ } else { fprintf( stderr, \" %02X\ } } i -= k; for ( j = 16 - k; j > 0; j-- ) { fprintf( stderr, \" \" ); } fprintf( stderr, \" \" ); for ( j = 0; j < k; j++, i++ ) { if ( ( byteArray[i] >= ' ' ) && ( byteArray[i] <= 255 ) ) { fprintf( stderr, \"%c\ } else { fprintf( stderr, \".\" ); } } fprintf( stderr, \"\\n\" ); return; } /* end of outputBinary */ static void pcap_callback ( u_char * none, const struct pcap_pkthdr * pcap_head, const u_char * packet ) { outputBinary( ( u_char * )packet, ( size_t )( pcap_head->caplen ) ); return; 24 } /* end of pcap_callback */ static pcap_t * pcap_init ( char * dev, char * filter, int snaplen, int timeout, int dumplevel ) { pcap_t * p = NULL; char errbuf[ PCAP_ERRBUF_SIZE ]; struct bpf_program bpf; bpf_u_int32 ip, mask; if ( dev == NULL ) { if ( ( dev = pcap_lookupdev( errbuf ) ) == NULL ) { fprintf( stderr, \"%s\\n\ exit( FAILURE ); } } fprintf( stderr, \"[ device --> %s ]\\n\ /* 1表示进入混杂模式 */ if ( ( p = pcap_open_live( dev, snaplen, 1, timeout, errbuf ) ) == NULL ) { fprintf( stderr, \"%s\\n\ exit( FAILURE ); } if ( pcap_lookupnet( dev, &ip, &mask, errbuf ) == -1 ) { exit( FAILURE ); } /* 1表示优化过滤规则 */ if ( pcap_compile( p, &bpf, filter, 1, mask ) < 0 ) { /* for example, pcap_compile: unknown ip proto ... */ pcap_perror( p, \"pcap_compile\" ); exit( FAILURE ); } if ( dumplevel >= 0 ) { bpf_dump( &bpf, dumplevel ); exit( SUCCESS ); } else if ( pcap_setfilter( p, &bpf ) == -1 ) { exit( FAILURE ); } 25 return( p ); } /* end of pcap_init */ static void pcap_read ( pcap_t * p ) { // static u_long count = 0; while ( 1 ) { pcap_dispatch( p, 1, pcap_callback, NULL ); // fprintf( stderr, \"count = %lu\\n\ // count++; } /* end of while */ return; } /* end of pcap_read */ static void sig_end ( int signo ) { fprintf( stderr, \"\\n\\nsig_end = %d\\n\ exit( SUCCESS ); } /* end of sig_end */ Sigfunc * signal ( int signo, Sigfunc * func ) { struct sigaction act, oact; act.sa_handler = func; sigemptyset( &act.sa_mask ); act.sa_flags = 0; if ( signo == SIGALRM ) { #ifdef SA_INTERRUPT act.sa_flags |= SA_INTERRUPT; /* SunOS 4.x */ #endif } else { #ifdef SA_RESTART act.sa_flags |= SA_RESTART; /* SVR4, 44BSD */ #endif } if ( sigaction( signo, &act, &oact ) < 0 ) { return( SIG_ERR ); 26 } return( oact.sa_handler ); } /* end of signal */ static Sigfunc * Signal ( int signo, Sigfunc * func ) /* for our signal() function */ { Sigfunc * sigfunc; if ( ( sigfunc = signal( signo, func ) ) == SIG_ERR ) { exit( FAILURE ); } return( sigfunc ); } /* end of Signal */ static void terminate ( void ) { if ( pcap_fd != NULL ) { pcap_close( pcap_fd ); } fprintf( stderr, \"\\n\" ); return; } /* end of terminate */ static void usage ( char * arg ) { fprintf( stderr, \" Usage: %s [-h] [-d dumplevel] [-i interface] [-s snaplen] [-t timeout]\\n\ exit( FAILURE ); } /* end of usage */ int main ( int argc, char * argv[] ) { char * dev = NULL; char filter[300] = \"\"; /* \"ip proto \\\cp and dst 192.168.8.90 and tcp[13] & 2 = 2\" */ int snaplen = LIBNET_ETH_H + LIBNET_IP_H + LIBNET_TCP_H; int timeout = 0; /* 值为0是否表示不设置读超时 */ int dumplevel = -1; int c, i; opterr = 0; /* don't want getopt() writing to stderr */ while ( ( c = getopt( argc, argv, \"d:hi:s:t:\" ) ) != EOF ) { switch ( c ) 27 { case 'd': dumplevel = atoi( optarg ); break; case 'i': dev = optarg; /* 指定网络接口设备 */ break; case 's': snaplen = atoi( optarg ); case 't': timeout = atoi( optarg ); break; case 'h': case '?': usage( argv[0] ); break; } /* end of switch */ } /* end of while */ argc -= optind; argv += optind; if ( argc > 0 ) { for ( i = 0; i < argc; i++ ) { if ( ( strlen( filter ) + strlen( argv[i] ) ) > 256 ) { fprintf( stderr, \"Checking your filter.\\n\" ); return( FAILURE ); } strcat( filter, argv[i] ); strcat( filter, \" \" ); } } fprintf( stderr, \"[ filter --> %s ]\\n\ Atexit( terminate ); for ( i = 1; i < 9; i++ ) { Signal( i, sig_end ); } Signal( SIGTERM, sig_end ); pcap_fd = pcap_init( dev, filter, snaplen, timeout, dumplevel ); pcap_read( pcap_fd ); return( SUCCESS ); } /* end of main */ 28 /*----------------------------------------------------------------------*/ -------------------------------------------------------------------------- Usage: ./pcap [-h] [-d dumplevel] [-i interface] [-s snaplen] [-t timeout] libpcap的好处还是很多,比如不需要为解析过滤规则耗费精力。这个程序再次演示了很多经典Unix编程技巧,比如getopt()、signal()、atexit(),回调函数部分没有做什么实际工作,看你自己发挥了。顺便提一句,即使是个小程序,也应该保持良好的风格,在华中看到太多不负责任的提问中的垃圾代码,实在是有辱C语言的传奇。 29
因篇幅问题不能全部显示,请点此查看更多更全内容
Copyright © 2019- axer.cn 版权所有 湘ICP备2023022495号-12
违法及侵权请联系:TEL:199 18 7713 E-MAIL:2724546146@qq.com
本站由北京市万商天勤律师事务所王兴未律师提供法律服务