您的位置:首页 > 其它

libpcap详解

2016-05-24 17:26 323 查看

libpcap(Packet Capture Library),即数据包捕获函数库,是Unix/Linux平台下的网络数据包捕获函数库。它是一个独立于系统的用户层包捕获的API接口,为底层网络监测提供了一个可移植的框架。

一、libpcap工作原理
libpcap主要由两部份组成:网络分接头(Network Tap)和数据过滤器(Packet Filter)。网络分接头从网络设备驱动程序中收集数据拷贝,过滤器决定是否接收该数据包。Libpcap利用BSD Packet Filter(BPF)算法对网卡接收到的链路层数据包进行过滤。BPF算法的基本思想是在有BPF监听的网络中,网卡驱动将接收到的数据包复制一份交给BPF过滤器,过滤器根据用户定义的规则决定是否接收此数据包以及需要拷贝该数据包的那些内容,然后将过滤后的数据给与过滤器相关联的上层应用程序。

libpcap的包捕获机制就是在数据链路层加一个旁路处理。当一个数据包到达网络接口时,libpcap首先利用已经创建的Socket从链路层驱动程序中获得该数据包的拷贝,再通过Tap函数将数据包发给BPF过滤器。BPF过滤器根据用户已经定义好的过滤规则对数据包进行逐一匹配,匹配成功则放入内核缓冲区,并传递给用户缓冲区,匹配失败则直接丢弃。如果没有设置过滤规则,所有数据包都将放入内核缓冲区,并传递给用户层缓冲区。

二、libpcap的抓包框架
pcap_lookupdev()函数用于查找网络设备,返回可被pcap_open_live()函数调用的网络设备名指针。
pcap_open_live()函数用于打开网络设备,并且返回用于捕获网络数据包的数据包捕获描述字。对于此网络设备的操作都要基于此网络设备描述字。
pcap_lookupnet()函数获得指定网络设备的网络号和掩码。
pcap_compile()函数用于将用户制定的过滤策略编译到过滤程序中。
pcap_setfilter()函数用于设置过滤器。
pcap_loop()函数pcap_dispatch()函数用于捕获数据包,捕获后还可以进行处理,此外pcap_next()和pcap_next_ex()两个函数也可以用来捕获数据包。

pcap_close()函数用于关闭网络设备,释放资源。

其实pcap的应用程序格式很简单,总的来说可以可以分为以下5部分,相信看了以下的5部分,大概能对pcap的总体布局有个大概的了解了吧:
1.我们从决定用哪一个接口进行嗅探开始。在Linux中,这可能是eth0,而在BSD系统中则可能是xl1等等。我们也可以用一个字符串来定义这个设备,或者采用pcap提供的接口名来工作。
2.初始化pcap。在这里我们要告诉pcap对什么设备进行嗅探。假如愿意的话,我们还可以嗅探多个设备。怎样区分它们呢?使用 文件句柄。就像打开一个文件进行读写一样,必须命名我们的嗅探“会话”,以此使它们各自区别开来。
3.假如我们只想嗅探特定的传输(如TCP/IP包,发往端口23的包等等),我们必须创建一个规则集合,编译并且使用它。这个过程分为三个相互紧密关联的阶段。规则集合被置于一个字符串内,并且被转换成能被pcap读的格式(因此编译它)。编译实际上就是在我们的程序里调用一个不被外部程序使用的函数。接下来我们要告诉 pcap使用它来过滤出我们想要的那一个会话。
4.最后,我们告诉pcap进入它的主体执行循环。在这个阶段内pcap一直工作到它接收了所有我们想要的包为止。每当它收到一个包就调用另一个已经定义好的函数,这个函数可以做我们想要的任何工作,它可以剖析所部获的包并给用户打印出结果,它可以将结果保存为一个文件,或者什么也不作。
5.在嗅探到所需的数据后,我们要关闭会话并结束。

三、实现libpcap的每一个步骤
(1)设置设备
这是很简单的。有两种方法设置想要嗅探的设备。
第一种,我们可以简单的让用户告诉我们。考察下面的程序:
   #include
   #include
   int main(int argc, char *argv[])
   {
   char *dev = argv[1];
   printf("Device: %s", dev);
   return(0);
   }
用户通过传递给程序的第一个参数来指定设备。字符串“dev”以pcap能“理解”的格式保存了我们要嗅探的接口的名字(当然,用户必须给了我们一个真正存在的接口)。
另一种也是同样的简单。来看这段程序:
   #include
   #include
   int main()
   {
   char *dev, errbuf[PCAP_ERRBUF_SIZE];
   dev = pcap_lookupdev(errbuf);
   printf("Device: %s", dev);
   return(0);
   }
(2)打开设备进行嗅探
创建一个嗅探会话的任务真的非常简单。为此,我们使用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)。此函数返回其会话句柄。
混杂模式与非混杂模式的区别:这两种方式区别很大。一般来说,非混杂模式的嗅探器中,主机仅嗅探那些跟它直接有关的通信,如发向它的,从它发出的,或经它路由的等都会被嗅探器捕捉。而在混杂模式中则嗅探传输线路上的所有通信。在非交换式网络中,这将是整个网络的通信。这样做最明显的优点就是使更多的包被嗅探到,它们因你嗅探网络的原因或者对你有帮助,或者没有。但是,混杂模式是可被探测到的。一个主机可以通过高强度的测试判定另一台主机是否正在进行混杂模式的嗅探。其次,它仅在非交换式的网络环境中有效工作(如集线器,或者交换中的ARP层面)。再次,在高负荷的网络中,主机的系统资源将消耗的非常严重。
(3)过滤通信
实现这一过程由pcap_compile()与pcap_setfilter()这两个函数完成。
在使用我们自己的过滤器前必须编译它。过滤表达式被保存在一个字符串中(字符数组)。其句法在tcpdump的手册页中被证实非常好。我建议你亲自阅读它。但是我们将使用简单的测试表达式,这样你可能很轻易理解我的例子。
我们调用pcap_compile()来编译它,其原型是这样定义的:
   int pcap_compile(pcap_t *p, strUCt bpf_program *fp, char *str, int optimize, bpf_u_int32 netmask)
第一个参数是会话句柄。接下来的是我们存储被编译的过滤器版本的地址的引用。再接下来的则是表达式本身,存储在规定的字符串格式里。再下边是一个定义表达式是否被优化的整形量(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[] = "eth0"; /* 执行嗅探的设备 */
   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的所有通信,使用混杂模式,设备是eth0。
(4)实际的嗅探
有两种手段捕捉包。我们可以一次只捕捉一个包,也可以进入一个循环,等捕捉到多个包再进行处理。我们将先看看怎样去捕捉单个包,然后再看看使用循环的方法。为此,我们使用函数pcap_next()。
pcap_next()的原型及其简单:
   u_char *pcap_next(pcap_t *p, struct pcap_pkthdr *h)
第一个参数是会话句柄,第二个参数是指向一个包括了当前数据包总体信息(被捕捉时的时间,包的长度,其被指定的部分长度)的结构体的指针(在这里只有一个片断,只作为一个示例)。pcap_next()返回一个u_char指针给被这个结构体描述的包。我们将稍后讨论这种实际读取包本身的手段。
   这里有一个演示怎样使用pcap_next()来嗅探一个包的例子:
   #include
   #include
   int main()
   {
   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]
   ", header.len);
   /* 关闭会话 */
   pcap_close(handle);
   return(0);
   }
这个程序嗅探被pcap_lookupdev()返回的设备并将它置为混杂模式。它发现第一个包经过端口23(telnet)并且告诉用户此包的大小(以字 节为单位)。这个程序又包含了一个新的调用pcap_close(),我们将在后面讨论(尽管它的名字就足够证实它自己的作用)。
实际上很少有嗅探程序会真正的使用pcap_next()。通常,它们使用pcap_loop()或者 pcap_dispatch()(它就是用了pcap_loop())。
pcap_loop()的原型如下:
   int pcap_loop(pcap_t *p, int cnt, pcap_handler callback, u_char *user)
第一个参数是会话句柄,接下来是一个整型,它告诉pcap_loop()在返回前应捕捉多少个数据包(若为负值则表示应该一直工作直至错误发生)。第三个参数是回调函数的名称(正像其标识符所指,无括号)。最后一个参数在有些应用里有用,但更多时候则置为NULL。假设我们有我们自己的想送往回调函数的参数,另外还有pcap_loop()发送的参数,这就需要用到它。很明显,必须是一个u_char类型的指针以确保结果正确;正像我们稍后见到的,pcap使用了很有意思的方法以u_char指针的形势传递信息。pcap_dispatch()的用法几乎相同。唯一不同的是它们如何处理超时(还记得在调用pcap_open_live()时怎样设置超时吗?这就是它起作用的地方)。Pcap_loop()忽略超时而pcap_dispatch()则不。关于它们之间区别的更深入的讨论请参见pcap的手册页。
回调函数的原型:
   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就是这些结构体的串联版本。为了使用它,我们必须作一些有趣的匹配工作。
下面这些是一些数据包的结构体:
   /* 以太网帧头部 */
   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
   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_FINTH_SYNTH_RSTTH_ACKTH_URGTH_ECETH_CWR)
   u_short th_win; /* TCP滑动窗口 */
   u_short th_sum; /* 头部校验和 */
   u_short th_urp; /* 紧急服务位 */
   };
pcap嗅探数据包时正是使用的这些结构。接下来,它简单的创建一个u_char字符串并且将这些结构体填入。那么我们怎样才能区分它们呢?预备好见证指针最实用的好处之一吧。
我们再一次假定要对以太网上的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()来确保尺寸的正确。这是因为这些结构体中的每个成员在不同平台下可以有不同的尺寸。

下面是主要函数接口:
pcap_t *pcap_open_live(char *device, int snaplen,
   int promisc, int to_ms, char *ebuf)
   获得用于捕获网络数据包的数据包捕获描述字。device参数为指定打开
   的网络设备名。snaplen参数定义捕获数据的最大字节数。promisc指定
   是否将网络接口置于混杂模式。to_ms参数指定超时时间(毫秒)。
   ebuf参数则仅在pcap_open_live()函数出错返回NULL时用于传递错误消
   息。
pcap_t *pcap_open_offline(char *fname, char *ebuf)
   打开以前保存捕获数据包的文件,用于读取。fname参数指定打开的文
   件名。该文件中的数据格式与tcpdump和tcpslice兼容。"-"为标准输
   入。ebuf参数则仅在pcap_open_offline()函数出错返回NULL时用于传
   递错误消息。
pcap_dumper_t *pcap_dump_open(pcap_t *p, char *fname)
   打开用于保存捕获数据包的文件,用于写入。fname参数为"-"时表示
   标准输出。出错时返回NULL。p参数为调用pcap_open_offline()或
   pcap_open_live()函数后返回的pcap结构指针。fname参数指定打开
   的文件名。如果返回NULL,则可调用pcap_geterr()函数获取错误消
   息。

char *pcap_lookupdev(char *errbuf)
   用于返回可被pcap_open_live()或pcap_lookupnet()函数调用的网络
   设备名指针。如果函数出错,则返回NULL,同时errbuf中存放相关的
   错误消息。
int pcap_lookupnet(char *device, bpf_u_int32 *netp,
   bpf_u_int32 *maskp, char *errbuf)
   获得指定网络设备的网络号和掩码。netp参数和maskp参数都是
   bpf_u_int32指针。如果函数出错,则返回-1,同时errbuf中存放相
   关的错误消息。
int pcap_dispatch(pcap_t *p, int cnt,
   pcap_handler callback, u_char *user)
   捕获并处理数据包。cnt参数指定函数返回前所处理数据包的最大值。
   cnt=-1表示在一个缓冲区中处理所有的数据包。cnt=0表示处理所有
   数据包,直到产生以下错误之一:读取到EOF;超时读取。callback
   参数指定一个带有三个参数的回调函数,这三个参数为:一个从
   pcap_dispatch()函数传递过来的u_char指针,一个pcap_pkthdr结构
   的指针,和一个数据包大小的u_char指针。如果成功则返回读取到的
   字节数。读取到EOF时则返回零值。出错时则返回-1,此时可调用
   pcap_perror()或pcap_geterr()函数获取错误消息。
int pcap_loop(pcap_t *p, int cnt,
   pcap_handler callback, u_char *user)
   功能基本与pcap_dispatch()函数相同,只不过此函数在cnt个数据包
   被处理或出现错误时才返回,但读取超时不会返回。而如果为
   pcap_open_live()函数指定了一个非零值的超时设置,然后调用
   pcap_dispatch()函数,则当超时发生时pcap_dispatch()函数会返回。
   cnt参数为负值时pcap_loop()函数将始终循环运行,除非出现错误。
void pcap_dump(u_char *user, struct pcap_pkthdr *h,
   u_char *sp)
   向调用pcap_dump_open()函数打开的文件输出一个数据包。该函数可
   作为pcap_dispatch()函数的回调函数。
int pcap_compile(pcap_t *p, struct bpf_program *fp,
   char *str, int optimize, bpf_u_int32 netmask)
   将str参数指定的字符串编译到过滤程序中。fp是一个bpf_program结
   构的指针,在pcap_compile()函数中被赋值。optimize参数控制结果
   代码的优化。netmask参数指定本地网络的网络掩码。
int pcap_setfilter(pcap_t *p, struct bpf_program *fp)
   指定一个过滤程序。fp参数是bpf_program结构指针,通常取自
   pcap_compile()函数调用。出错时返回-1;成功时返回0。
u_char *pcap_next(pcap_t *p, struct pcap_pkthdr *h)
   返回指向下一个数据包的u_char指针。
int pcap_datalink(pcap_t *p)
   返回数据链路层类型,例如DLT_EN10MB。
int pcap_snapshot(pcap_t *p)
   返回pcap_open_live被调用后的snapshot参数值。
int pcap_is_swapped(pcap_t *p)
   返回当前系统主机字节与被打开文件的字节顺序是否不同。
int pcap_major_version(pcap_t *p)
   返回写入被打开文件所使用的pcap函数的主版本号。
int pcap_minor_version(pcap_t *p)
   返回写入被打开文件所使用的pcap函数的辅版本号。
int pcap_stats(pcap_t *p, struct pcap_stat *ps)
   向pcap_stat结构赋值。成功时返回0。这些数值包括了从开始
   捕获数据以来至今共捕获到的数据包统计。如果出错或不支持
   数据包统计,则返回-1,且可调用pcap_perror()或
   pcap_geterr()函数来获取错误消息。
FILE *pcap_file(pcap_t *p)
   返回被打开文件的文件名。
int pcap_fileno(pcap_t *p)
   返回被打开文件的文件描述字号码。
void pcap_perror(pcap_t *p, char *prefix)
   在标准输出设备上显示最后一个pcap库错误消息。以prefix参
   数指定的字符串为消息头。
char *pcap_geterr(pcap_t *p)
   返回最后一个pcap库错误消息。
char *pcap_strerror(int error)
   如果strerror()函数不可用,则可调用pcap_strerror函数替代。
void pcap_close(pcap_t *p)
   关闭p参数相应的文件,并释放资源。
四: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;

    ????????????????????????????????

u_short version_major;

    Libpcap的主版本号。

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;

    被驱动程序丢弃的数据报的数目;

u_int ps_ifdrop;

    被网卡丢弃的数据报的数目;

struct pcap_sf {    //pacap的savefile结构 定义

        FILE *rfile;    //该指针指向savefile文件

        int swapped;    //?

        int hdrsize;    //头大小吗?

        int version_major;//主版本号

        int version_minor;//从版本号

        u_char *base;//?

};

struct pcap_md { //?

        struct pcap_stat stat;

        /*XXX*/

        int use_bpf;            /* using kernel filter */

        u_long  TotPkts;        /* can't oflow for 79 hrs on ether */

        u_long  TotAccepted;    /* count accepted by filter */

        u_long  TotDrops;       /* count of dropped packets */

        long    TotMissed;      /* missed by i/f during this run */

        long    OrigMissed;     /* missed by i/f before this run */

#ifdef linux

        int     sock_packet;    /* using Linux 2.0 compatible interface */

        int     timeout;        /* timeout specified to pcap_open_live */

        int     clear_promisc;  /* must clear promiscuous mode when we close */

        int     cooked;         /* using SOCK_DGRAM rather than SOCK_RAW */

        int     lo_ifindex;     /* interface index of the loopback device */

        char    *device;        /* device name */

        struct pcap *next;      /* list of open promiscuous sock_packet pcaps */

#endif

};

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;

        /*

         * Read buffer.

         */

        int bufsize;

        u_char *buffer;

        u_char *bp;

        int cc;

        /*

         * Place holder for pcap_next().

         */

        u_char *pkt;

        

        /*

         * Placeholder for filter code if bpf not in kernel.

         */

        struct bpf_program fcode;

        char errbuf[PCAP_ERRBUF_SIZE];

};

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()函数的定义,了解如何打开一个预先保存的包含数据报的文件的细节。

    参数:

    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 * p:pcap_open_live返回的数据报捕获的指针;

    int cnt:规定了函数返回前应处理的数据报数目;

    pcap_handler callback:指向一个用户自定义的函数,在处理每个报后自动调用该函数进行再处理;

    u_char *user:该指针用于传递给callback.(不知道有什么用?)

    返回值:

    如果函数成功执行(包括读文件时读到EOF),则返回0.否则返回-1,那么错误信息将由函数pcap_geterr或pcap_perror给出;

    补充:callback函数:

    The concept behind a callback function is fairly simple.  Suppose I have a program that is waiting for an event of some sort.  For the purpose of this example, lets pretend that my program wants a user to press a key on the keyboard.  Every time they press
a key, I want to call a function which then will determine that to do.  The function I am utilizing is a callback function.

pcap_open_dead()

     is used for creating a pcap_t structure to use when calling the other functions in libpcap. It is typically used when just using libpcap for compiling BPF code.

pcap_dump_open()

     is called to open a ``savefile'' for writing. The name "-" in a synonym for stdout. NULL is returned on failure. p is a pcap struct as returned by pcap_open_offline() or pcap_open_live(). fname specifies the name of the file to open. If NULL is returned,
pcap_geterr() can be used to get the error text.

pcap_setnonblock()

     puts a capture descriptor, opened with pcap_open_live(), into ``non-blocking'' mode, or takes it out of ``non-blocking'' mode, depending on whether the nonblock argument is non-zero or zero. It has no effect on ``savefiles''. If there is an error, -1 is
returned and errbuf is filled in with an appropriate error message; otherwise, 0 is returned. In ``non-blocking'' mode, an attempt to read from the capture descriptor with pcap_dispatch() will, if no packets are currently available to be read, return 0 immediately
rather than blocking waiting for packets to arrive. pcap_loop() and pcap_next() will not work in ``non-blocking'' mode.

    libpcap文件选读:

    ethernet.c:定义了三个内联函数:

    static inline int xdtoi(int):该函数将十六进值数转化为整数;

    static inline int skip_space(FILE *):该函数定义了在一个文件中含有空格时,返回第一个不是' '的字符。

    static inline int skip_line(FILE *):

    struct pcap_etherent {

            u_char addr[6];

            char name[122];

        };

    ethertype.h定义了各个协议的值。如#define ETHERTYPE_IP            0x0800  /* IP protocol */
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  lipcap 协议分析