WinpCap的详解(二)

转自 : http://www.cnblogs.com/yingfang18/archive/2010/11/27/1889659.html


1、不用回调函数捕获数据

  pcap_loop()函数是基于回调的原理来进行数据捕获,这是一种精妙的方法,并且在某些场合中,它是一种很好的选择。 然而,处理回调有时候并不实用 -- 它会增加程序的复杂度,特别是在拥有多线程的C++程序中。

  可以通过直接调用pcap_next_ex() 函数来获得一个数据包 -- 只有当编程人员使用了 pcap_next_ex() 函数才能收到数据包。这个函数的参数和捕获回调函数的参数是一样的 -- 它包含一个网络适配器的描述符和两个可以初始化和返回给用户的指针 (一个指向 pcap_pkthdr 结构体,另一个指向数据报数据的缓冲)。

  在下面的程序中,我们会再次用到上一讲中的有关回调方面的代码,只是我们将它放入了main()函数,之后调用 pcap_next_ex()函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
#include "pcap.h"
 
 
main()
{
pcap_if_t *alldevs;
pcap_if_t *d;
int  inum;
int  i=0;
pcap_t *adhandle;
int  res;
char  errbuf[PCAP_ERRBUF_SIZE];
struct  tm  *ltime;
char  timestr[16];
struct  pcap_pkthdr *header;
const  u_char *pkt_data;
time_t  local_tv_sec;
     
     
     /* 获取本机设备列表 */
     if  (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &alldevs, errbuf) == -1)
     {
         fprintf (stderr, "Error in pcap_findalldevs: %s\n" , errbuf);
         exit (1);
     }
     
     /* 打印列表 */
     for (d=alldevs; d; d=d->next)
     {
         printf ( "%d. %s" , ++i, d->name);
         if  (d->description)
             printf ( " (%s)\n" , d->description);
         else
             printf ( " (No description available)\n" );
     }
     
     if (i==0)
     {
         printf ( "\nNo interfaces found! Make sure WinPcap is installed.\n" );
         return  -1;
     }
     
     printf ( "Enter the interface number (1-%d):" ,i);
     scanf ( "%d" , &inum);
     
     if (inum < 1 || inum > i)
     {
         printf ( "\nInterface number out of range.\n" );
         /* 释放设备列表 */
         pcap_freealldevs(alldevs);
         return  -1;
     }
     
     /* 跳转到已选中的适配器 */
     for (d=alldevs, i=0; i< inum-1 ;d=d->next, i++);
     
     /* 打开设备 */
     if  ( (adhandle= pcap_open(d->name,          // 设备名
                               65536,            // 要捕捉的数据包的部分
                                                 // 65535保证能捕获到不同数据链路层上的每个数据包的全部内容
                               PCAP_OPENFLAG_PROMISCUOUS,    // 混杂模式
                               1000,             // 读取超时时间
                               NULL,             // 远程机器验证
                               errbuf            // 错误缓冲池
                               ) ) == NULL)
     {
         fprintf (stderr, "\nUnable to open the adapter. %s is not supported by WinPcap\n" , d->name);
         /* 释放设列表 */
         pcap_freealldevs(alldevs);
         return  -1;
     }
     
     printf ( "\nlistening on %s...\n" , d->description);
     
     /* 释放设备列表 */
     pcap_freealldevs(alldevs);
     
     /* 获取数据包 */
     while ((res = pcap_next_ex( adhandle, &header, &pkt_data)) >= 0){
         
         if (res == 0)
             /* 超时时间到 */
             continue ;
         
         /* 将时间戳转换成可识别的格式 */
         local_tv_sec = header->ts.tv_sec;
         ltime= localtime (&local_tv_sec);
         strftime ( timestr, sizeof  timestr, "%H:%M:%S" , ltime);
         
         printf ( "%s,%.6d len:%d\n" , timestr, header->ts.tv_usec, header->len);
     }
     
     if (res == -1){
         printf ( "Error reading the packets: %s\n" , pcap_geterr(adhandle));
         return  -1;
     }
     
     return  0;
}
注:程序里面解释的很详细啊,不用再解释程序里面的含义了吧!

  为什么我们要用 pcap_next_ex() 代替以前的 pcap_next()? 因为 pcap_next() 有一些不好的地方。首先,它效率低下,尽管它隐藏了回调的方式,但它依然依赖于函数 pcap_dispatch()。第二,它不能检测到文件末尾这个状态(EOF),因此,如果数据包是从文件读取来的,那么它就不那么有用了。

2、过滤数据包

  WinPcap和Libpcap的最强大的特性之一,是拥有过滤数据包的引擎。 它提供了有效的方法去获取网络中的某些数据包,这也是WinPcap捕获机制中的一个组成部分。 用来过滤数据包的函数是 pcap_compile() 和 pcap_setfilter() 。

  pcap_compile() 它将一个高层的布尔过滤表达式编译成一个能够被过滤引擎所解释的低层的字节码。

  pcap_setfilter() 将一个过滤器与内核捕获会话向关联。当 pcap_setfilter() 被调用时,这个过滤器将被应用到来自网络的所有数据包,并且,所有的符合要求的数据包 (即那些经过过滤器以后,布尔表达式为真的包) ,将会立即复制给应用程序。

  以下代码展示了如何编译并设置过滤器。 请注意,我们必须从 pcap_if 结构体中获得掩码,因为一些使用 pcap_compile() 创建的过滤器需要它。

  在这段代码片断中,传递给 pcap_compile() 的过滤器是"ip and tcp",这说明我们只希望保留IPv4和TCP的数据包,并把他们发送给应用程序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
if  (d->addresses != NULL)
         /* 获取接口第一个地址的掩码 */
         netmask=(( struct  sockaddr_in *)(d->addresses->netmask))->sin_addr.S_un.S_addr;
     else
         /* 如果这个接口没有地址,那么我们假设这个接口在C类网络中 */
         netmask=0xffffff;
 
 
compile the filter
     if  (pcap_compile(adhandle, &fcode, "ip and tcp" , 1, netmask) < 0)
     {
         fprintf (stderr, "\nUnable to compile the packet filter. Check the syntax.\n" );
         /* 释放设备列表 */
         pcap_freealldevs(alldevs);
         return  -1;
     }
     
set the filter
     if  (pcap_setfilter(adhandle, &fcode) < 0)
     {
         fprintf (stderr, "\nError setting the filter.\n" );
         /* 释放设备列表 */
         pcap_freealldevs(alldevs);
         return  -1;
     }

3、分析数据包(UDP)  

  当我们能够将数据存在一个数组中的时候,就需要对这个数据进行分析,这里,先分析一下UDP数据,关于一帧数据的格式,包括MAC、ip头、udp头,等有空再写一篇吧。来看看下面的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
#include "pcap.h"
 
/* 4字节的IP地址 */
typedef  struct  ip_address{
     u_char byte1;
     u_char byte2;
     u_char byte3;
     u_char byte4;
}ip_address;
 
/* IPv4 首部 */
typedef  struct  ip_header{
     u_char  ver_ihl;        // 版本 (4 bits) + 首部长度 (4 bits)
     u_char  tos;            // 服务类型(Type of service)
     u_short tlen;           // 总长(Total length)
     u_short identification; // 标识(Identification)
     u_short flags_fo;       // 标志位(Flags) (3 bits) + 段偏移量(Fragment offset) (13 bits)
     u_char  ttl;            // 存活时间(Time to live)
     u_char  proto;          // 协议(Protocol)
     u_short crc;            // 首部校验和(Header checksum)
     ip_address  saddr;      // 源地址(Source address)
     ip_address  daddr;      // 目的地址(Destination address)
     u_int   op_pad;         // 选项与填充(Option + Padding)
}ip_header;
 
/* UDP 首部*/
typedef  struct  udp_header{
     u_short sport;          // 源端口(Source port)
     u_short dport;          // 目的端口(Destination port)
     u_short len;            // UDP数据包长度(Datagram length)
     u_short crc;            // 校验和(Checksum)
}udp_header;
 
/* 回调函数原型 */
void  packet_handler(u_char *param, const  struct  pcap_pkthdr *header, const  u_char *pkt_data);
 
 
main()
{
pcap_if_t *alldevs;
pcap_if_t *d;
int  inum;
int  i=0;
pcap_t *adhandle;
char  errbuf[PCAP_ERRBUF_SIZE];
u_int netmask;
char  packet_filter[] = "ip and udp" ;
struct  bpf_program fcode;
 
     /* 获得设备列表 */
     if  (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &alldevs, errbuf) == -1)
     {
         fprintf (stderr, "Error in pcap_findalldevs: %s\n" , errbuf);
         exit (1);
     }
     
     /* 打印列表 */
     for (d=alldevs; d; d=d->next)
     {
         printf ( "%d. %s" , ++i, d->name);
         if  (d->description)
             printf ( " (%s)\n" , d->description);
         else
             printf ( " (No description available)\n" );
     }
 
     if (i==0)
     {
         printf ( "\nNo interfaces found! Make sure WinPcap is installed.\n" );
         return  -1;
     }
     
     printf ( "Enter the interface number (1-%d):" ,i);
     scanf ( "%d" , &inum);
     
     if (inum < 1 || inum > i)
     {
         printf ( "\nInterface number out of range.\n" );
         /* 释放设备列表 */
         pcap_freealldevs(alldevs);
         return  -1;
     }
 
     /* 跳转到已选设备 */
     for (d=alldevs, i=0; i< inum-1 ;d=d->next, i++);
     
     /* 打开适配器 */
     if  ( (adhandle= pcap_open(d->name,  // 设备名
                              65536,     // 要捕捉的数据包的部分
                                         // 65535保证能捕获到不同数据链路层上的每个数据包的全部内容
                              PCAP_OPENFLAG_PROMISCUOUS,         // 混杂模式
                              1000,      // 读取超时时间
                              NULL,      // 远程机器验证
                              errbuf     // 错误缓冲池
                              ) ) == NULL)
     {
         fprintf (stderr, "\nUnable to open the adapter. %s is not supported by WinPcap\n" );
         /* 释放设备列表 */
         pcap_freealldevs(alldevs);
         return  -1;
     }
     
     /* 检查数据链路层,为了简单,我们只考虑以太网 */
     if (pcap_datalink(adhandle) != DLT_EN10MB)
     {
         fprintf (stderr, "\nThis program works only on Ethernet networks.\n" );
         /* 释放设备列表 */
         pcap_freealldevs(alldevs);
         return  -1;
     }
     
     if (d->addresses != NULL)
         /* 获得接口第一个地址的掩码 */
         netmask=(( struct  sockaddr_in *)(d->addresses->netmask))->sin_addr.S_un.S_addr;
     else
         /* 如果接口没有地址,那么我们假设一个C类的掩码 */
         netmask=0xffffff;
 
 
     //编译过滤器
     if  (pcap_compile(adhandle, &fcode, packet_filter, 1, netmask) <0 )
     {
         fprintf (stderr, "\nUnable to compile the packet filter. Check the syntax.\n" );
         /* 释放设备列表 */
         pcap_freealldevs(alldevs);
         return  -1;
     }
     
     //设置过滤器
     if  (pcap_setfilter(adhandle, &fcode)<0)
     {
         fprintf (stderr, "\nError setting the filter.\n" );
         /* 释放设备列表 */
         pcap_freealldevs(alldevs);
         return  -1;
     }
     
     printf ( "\nlistening on %s...\n" , d->description);
     
     /* 释放设备列表 */
     pcap_freealldevs(alldevs);
     
     /* 开始捕捉 */
     pcap_loop(adhandle, 0, packet_handler, NULL);
     
     return  0;
}
 
/* 回调函数,当收到每一个数据包时会被libpcap所调用 */
void  packet_handler(u_char *param, const  struct  pcap_pkthdr *header, const  u_char *pkt_data)
{
     struct  tm  *ltime;
     char  timestr[16];
     ip_header *ih;
     udp_header *uh;
     u_int ip_len;
     u_short sport,dport;
     time_t  local_tv_sec;
 
     /* 将时间戳转换成可识别的格式 */
     local_tv_sec = header->ts.tv_sec;
     ltime= localtime (&local_tv_sec);
     strftime ( timestr, sizeof  timestr, "%H:%M:%S" , ltime);
 
     /* 打印数据包的时间戳和长度 */
     printf ( "%s.%.6d len:%d " , timestr, header->ts.tv_usec, header->len);
 
     /* 获得IP数据包头部的位置 */
     ih = (ip_header *) (pkt_data +
         14); //以太网头部长度
 
     /* 获得UDP首部的位置 */
     ip_len = (ih->ver_ihl & 0xf) * 4;
     uh = (udp_header *) ((u_char*)ih + ip_len);
 
     /* 将网络字节序列转换成主机字节序列 */
     sport = ntohs( uh->sport );
     dport = ntohs( uh->dport );
 
     /* 打印IP地址和UDP端口 */
     printf ( "%d.%d.%d.%d.%d -> %d.%d.%d.%d.%d\n" ,
         ih->saddr.byte1,
         ih->saddr.byte2,
         ih->saddr.byte3,
         ih->saddr.byte4,
         sport,
         ih->daddr.byte1,
         ih->daddr.byte2,
         ih->daddr.byte3,
         ih->daddr.byte4,
         dport);
}

  一般情况下,一帧数据包括12字节的MAC头,还有两个字节表示的是0x8000表示的是ip头,ih = (ip_header *) (pkt_data +14); 这里面的14=12+2得来的,就得到ip头,ip头+ip数据头的长度,就到了UDP数据头。也就是一个数据包分为MAC+IP+UDP+数据,符合TCP/IP协议里面的网络接口层+网络层+传输层的概念。

待续.......

 


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
WinPcap是一款在Windows操作系统上的网络数据包捕获和分析工具。它提供了发送和接收网络数据包的功能,包括发送层数据包。 发送层数据包意味着通过网络适配器发送构建的数据包,这些数据包在以太网帧中传输。WinPcap提供了一个名为SendPacket的函数,可以用于发送构建好的层数据包。 在使用WinPcap发送层数据包之前,我们需要做以下准备工作: 1. 获取网络适配器:通过调用WinPcap的函数,我们可以列出系统上所有可用的网络适配器,并选择要使用的适配器。 2. 构建数据包:我们需要创建一个符合层协议的数据包。这意味着我们需要构建一个以太网帧,并在帧的头部设置目标MAC地址和源MAC地址。 3. 打开适配器:在发送数据包之前,我们需要打开选择的网络适配器。 4. 发送数据包:调用SendPacket函数将构建好的层数据包发送到网络。 5. 关闭适配器和释放资源:在发送完数据包后,我们应该关闭适配器并释放相关的资源。 需要注意的是,使用WinPcap发送层数据包需要具备一定的网络知识和编程经验,了解以太网协议以及数据包的结构和组成。此外,发送层数据包可能需要管理员权限,因为它涉及到操作网络适配器。 总结起来,WinPcap可以帮助我们在Windows环境下发送层数据包,通过构建以太网帧和设置目标MAC地址和源MAC地址来发送数据包。但是操作时需要小心谨慎,避免对网络造成不必要的干扰或安全风险。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值