第一天
转载请注明原文出处: http://blogger.org.cn/blog/more.asp?name=binaryluo&id=11458
说明:本系列文章是我阅读winpcap手册后整理的一个学习笔记。文章中出现的所有代码是我根据winpcap手册中的示例代码进行了学习,并调 试通过,其中对部分代码作了修改,关于代码的版权我尊重winpcap手册中的版权说明,如果你使用了本系列文章中的代码而引起任何的版权或造成安全威胁 等问题,我将不负任何责任。
下载好了WpdPack_3_2_alpha1.zip(下载地址: http://www.winpcap.org/install/bin/WpdPack_3_2_alpha1.zip ), 解压后除了有文档,例子外还有Include和lib,于是想用TC2来做开发环境,但是编译的时候老是出问题,于是放弃。后来阅读了Winpcap手册 后才知道因为是在windows上开发,所以它推荐用VC++6.0,于是改用VC。
第一个试验是:
编译的时候又遇到问题——“无法打开pcap.h”。又查看开发手册才找到解决方法:
1.安装Winpcap驱动。下载地址: http://www.winpcap.org/install/bin/WinPcap_3_1.exe 。
2.将Winpcap的Include,Lib目录添加进VC6.0的环境变量中;
3. 针对每一个项目,先用VC打开项目,然后在"Project->Settings",标签栏出选择"C/C++",在"Preprocessor definitions"的输入框里添加"WPCAP",再选择"Link",在"Object/library modules"的输入框里添加"wpcap.lib Packet.lib"。
再编译时终于OK了。 之后,阅读代码并查看开发手册学到了下面的东西:
pcap_if 是一个结构体,具体点它是一个链表的结点,他的定义如下:
struct pcap_if {
struct pcap_if *next;
char *name;
char *description;
struct pcap_addr *addresses;
u_int flags;
}
另外,在 pcap.h 中有一句“ typedef struct pcap_if pcap_if_t ; ”,所以也可以用 pcap_if_t 代替 pcap_if 。
int pcap_findalldevs_ex(
char * source,
struct pcap_rmtauth * auth,
pcap_if_t ** alldevs,
char * errbuf
)
这个函数是 ’pcap_findalldevs()’ 的一个超集。 ’pcap_findalldevs()’ 比较老,他只允许列出本地机器上的设备。然而, ’pcap_findalldevs_ex()’ 除了可以列出本地及其上的设备,还可以列出远程机器上的设备。此外,它还能列出所有可用的 pcap 文件到指定的文件夹。 ’pcap_findalldevs_ex()’ 是平台无关的,然而它以来于标准的 ’pcap_findalldevs()’ 来获得本地机器的地址。
精彩评论
问: linux 下有无此类可以将包截取而不是仅仅获得包的一份拷贝的接口呢
答: winpcap本身就不影响数据栈的阿! 在linux有libpcap这个库和winpcap很类似的。我的blog上有libpcap的转载资料(psbinaryluo 我不是在和你抢生意哦,嘿嘿)。libpcap基本适用于任何unix系统linux当然不例外。
问: 在NDIS微端口层实现数据包的截获与分析,并对数据包进行分类。当前的抓包程序种类比较多,但总结起来不外乎应用层抓包和NDIS中间层抓包两种。在开发NDIS中间层驱动时,使用现有的抓包程序往往会出现抓包程序与所开发的驱动绑定层次混乱的现象。本课题从这一实际问题入手,利用自研的专用网卡及其驱动程序开发出一套在NDIS中间层之下的抓包程序,它将为进一步研究和开发NDIS驱动提供极大的支持。
第二天
转载请注明原文出处: http://blogger.org.cn/blog/more.asp?name=binaryluo&id=11459
今天在阅读 Winpcap Manual 的时候发现一句话:
“ This means that on shared media (like non-switched Ethernet), WinPcap will be able to capture the packets of other hosts.”
我理解为:如果在通过没有交换功能的集线器连接的网络上,只要把网卡设置为混杂( promiscuous )模式, winpcap 能够捕获到其他主机通信的数据 包。如果是具有交换功能的集线器连接的网络 winpcap 还能管用吗?这个在后边的实习 中将会进行试验。
试验程序2:
函数 1 :
pcap_t *pcap_open(const char * source,
int snaplen,
int flags,
int read_timeout,
struct pcap_rmtauth * auth,
char * errbuf
)
为捕获 / 发送数据打开一个普通的源。 pcap_open() 能够替代所有的 pcap_open_xxx() 函数,它隐藏了不同的 pcap_open_xxx() 之间的差异,所以程序员不必使用不同的 open 函数。
source :的是包含要打开的源名称的以 ’/0’ 结尾的字符串。源名称得包含新的源规范语法 ( Source Specification Syntax ) ,并且它不能为 NULL 。为了方便的使 用源语法,请记住:( 1 ) pcap_findalldevs_ex() 返 回的适配器(网卡)可以直接被 pcap_open() 使用;( 2 )万一用户想传递他自己的源字符串给 pcap_open() , pcap_createsrcstr() 可以 创建正确的源标识。
snaplen :需要保留的数据包的长度。对每一个过滤器接收到的数据包,第一个‘ snaplen ’字节的内容将被保存到缓冲区,并且传递给用户程序。例如, snaplen 等于 100 ,那么仅仅每一个数据包的第一个 100 字节的内容被保存。简言之就是从每一个包的开头到 snaplen 的那段内容将被保存。
flags :保存一些由于抓包需要的标志。 Winpcap 定义了 三种标志:
l PCAP_OPENFLAG_PROMISCUOUS : 1 ,它定义了适配器(网卡)是否进入混杂模式 ( promiscuous mode )。
l PCAP_OPENFLAG_DATATX_UDP : 2 ,它定义了数据传输(假如是远程抓包)是否 用 UDP 协议来处理。
l PCAP_OPENFLAG_NOCAPTURE_RPCAP : 4 ,它定义了远程探测器是否捕获它自己产生的 数据包。
read_timeout :以毫秒为单位。 read timeout 被 用来设置在遇到一个数据包的时候读操作不必立即返回,而是等待一段时间,让更多的数据包到来后从 OS 内核一次读多个数据包。并非所有的平台都支持 read timeout ;在不支持 read timeout 的平台上它将被忽 略。
auth :一个指向 ’struct pcap_rmtauth’ 的 指针,保存当一个用户登录到某个远程机器上时的必要信息。假如不是远程抓包,该指针被设置为 NULL 。
errbuf :一个指向用户申请的缓冲区的指针,存放当该函数出错时的错误信息。
返回值是一个 ’pcap_t’ 指针, 它可以作为下一步调用(例如 pcap_compile() 等)的参数,并且指定了一个已经打开的 Winpcap 会话。在遇到问题的情况下,它返回 NULL 并且 ’errbuf’ 变量保存了错误信息。
函数 2 :
int pcap_loop( pcap_t* p,
int cnt,
pcap_hander callback,
u_char* user
)
收集一群数据包。 pcap_loop() 与 pcap_dispatch() 类似,但是它会一直保持读数据包的操作直到 cnt 包被处理或者发生了错误。当有活动的读超时( read timeout )时它并不返回。然而,对 pcap_open_live() 指定一个非 0 的读超时 (read timeout) ,当发生超时 的时候调用 pcap_dispatch() 来接收并处理到来的所有数据包更好。 Cnt 指明了返回之前要处理数据包的最大数目。如果 cnt 为负值, pcap_loop() 将一直循环(直到发生 错误才停止)。如果出错时返回- 1 ;如果 cnt 用完时返回 0 ;如果在任何包被处理前调用 pcap_breakloop() 来中止循环将返回- 2 。所以,如果程序中使用了 pcap_breakloop() ,必须准确的来判断返回值是- 1 还是- 2 ,而不能简单的判断 <0 。
函数 3 :
hypedef void (* pcap_handler)(u_char* user,
const struct pcap_pkthdr* pkt_header,
const u_char* pkt_data)
接收数据包的回调函数原型。当用户程序使用 pcap_dispatch() 或者 pcap_loop() ,数据包以这种回调的 方法传给应用程序。用户参数是用户自己定义的包含捕获会话状态的参数,它必须跟 pcap_dispatch() 和 pcap_loop() 的参数相一致。 pkt_hader 是与 抓包驱动有关的头。 pkt_data 指向包里的数据,包括协议头。
结构体 1 :
struct pcap_pkthdr {
struct timeval ts;
bpf_u_int32 caplen;
bpf_u_int32 len;
}
ts :时间戳
cpalen :当前分组的长度
len :数据包的长度
精彩评论
问: 在运行上面代码时候出现下面错误,是怎么回事? pcap.obj : error LNK2001: unresolved external symbol "struct pcap * __cdecl pcap_open(char const *,int,int,int,struct pcap_rmtauth *,char *)" (?pcap_open@@YAPAUpcap@@PBDHHHPAUpcap_rmtauth@@PAD@Z)Debug/pcap.exe : fatal error LNK1120: 1 unresolved externalsError executing link.exe.
答: 编译环境没有配置
问: 请问端口镜像,就是在交换机中winpcap怎么用?也就是想固定捕获同局域网内某ip的网络数据包。
答: 应该怎么程序实现Winpcap要捕获网络上的数据包主要是通过把网卡模式设置为混杂模式,而且必须是在 广播网中才可以获得其它主机的数据包。如果是在交换网中,即使把网卡模式设置为混杂了也不能捕获到其它主机的数据包。
再问: 谢谢关注,那在交换网中winpcap怎么用? 现在我用winpcap写一个分析工具,我希望在工具上选择同局域网的一个ip:192.168.*.*[通过交换机连接],这时要求工具上只显示流经此机器的数据包。这个好像还要在交换机上设置端口镜像,而在程序上需不需要另外改动?
第三天
今天的试验程序与前天的功能是一样的,只是在捕获数据包的时候前天的 程序用的是pcap_loop(), 今天的代码用的是pcap_next_ex ()。基于 pcap_loop() 抓包机制的回调很方便 而且在某些情况下是一个不错的选择。但是,处理回调有些时候不适用——它使得程序更复杂,尤其是在应用程序与多线程或 C++ 类有关的情况下。而pcap_next_ex ()有的时候用起来更加方便。
试验代码3:
函数 1 :
pcap_next_ex(pcap_t* p,
struct pcap_pkthdr** pkt_header,
const u_char* pkt_data
)
从一个网络接口或离线捕获方式(例如读文件)读取一个数据包。该函数被用来重新获得下一个可用的数据包,没有使用 libpcap 提供的传统的回调方法。 pcap_next_ex 用 指向头和下一个被捕获的数据包的指针为 pkt_header 和 pkt_data 参数赋值。
返回值有下列几种情况:
1 ,数据包被正确读取
0 , pcap_open_live() 设置的超时 时间到。在这种情况下 pkt_header 和 pkt_data 不指向有效数据包
- 1 ,发生错误
- 2 ,离线捕获的时候读取到 EOF
我们通常使用 pcap_next_ex() 而不是 pcap_next() ,因为 pcap_next() 有些缺点。首先, pcap_next() 效率低,因为它隐藏了回调方法但是还是依赖于 pcap_dispatch ;第二,它不能检测 EOF ,所以当从一个文 件获取数据包时它不是很有用。
函数 2 :
u_char* pcap_next(pcap_t* p,
struct pcap_pkthdr* h
)
返回下一个可用的数据包并且返回一个 u_char 指向该数据包数据部分的指针。如果发生错误或者活动的抓包没有读取到数据包(例如:数据包不能通过包 过滤器而被丢弃,或者在支持读超时( read timeout )的平台上在任何数据包到来之前就超时终止,又或者是抓 包设备的文件描述符在非阻塞( non-blocking )模式下并且没有数据包可以被读取),或者文件已被读完时返回 NULL 。不幸的是,没有办法检测是否发生错误。
精彩评论
问: c:/Documents and Settings/cheli/My Documents/Visual Studio Projects/disan/disan/disan.cpp(86): error C2664: 'pcap_next_ex' : 3第三个引数 'u_char **__w64 ' 里来的 'const u_char ** ' 类型不能转换。 c:/Documents and Settings/cheli/My Documents/Visual Studio Projects/disan/disan/disan.cpp(86): fatal error C1903: 前面的错误没有修复。编译终止。 我用的是Microsoft Visual Studio .NET 2003
答: 可能是那个数据类型没有被定义过,也就是Winpcap的库没有被包含进去。
再问: 我是在.net下面得。 所以没有安装Winpcap驱动。而是手工添加的。
后来把pcap.h里面的int pcap_next_ex(pcap_t *, struct pcap_pkthdr **, u_char **); 改为int pcap_next_ex(pcap_t *, struct pcap_pkthdr **, /*const*/ u_char **); 就编译通过了。把pcap.h里面的Const u_char**改为u_char**即可,我在6.0的环境下也遇到了同样的问题。
答: //u_char *pktdata; const u_char *pktdata;//编译报错,需要修改 这样修改就可以了
问: 我想知道第86行里面while ((res = pcap_next_ex(adhandle, &header, &pkt_data)) >= 0)的第3个参数&pkt_data的值是哪个函数的什么参数传给他的?
答: pkt_data表示的是pcap_next_ex()捕获的数据包,相当于是pcap_next_ex()的返回值。
问: 在.net下手工添加winpcap 的步骤说的详细一点。
答: 但我没有找到在工程添加的方法只能先 这样了。 我运行程序他会把报告错误,然后你点那个错误,找到错误是在那一行,比如指向 #include <pcap.h〉 你看看pcap.h外面的是<>还是 #include "pcap.h" 如果是<>就改成" "因为<>不会查找用户自己添加的.h文件。 该好以后你就在工程的树行结构里面的“头文件文件夹”(我用的是日语版本的,我是这么翻译的,应该就是类似的叫法)右键电击追加,选择已存在的文件。把刚 报错的.h文件追加进去,就按这种方法一个追加。 最后把 Packet.lib 和瓦wpcap.lib这两个库文件添加进去。
问: 上面那个程序在循环多次抓包以后,内存占用会越来越大,增长的很快,而在pcap_next_ex(adhandle, &header, &pkt_data)中的pkt_data是const char *,是不断变化的,说明此字符串是不断增长的,有什么方法解决这个问题?多谢各位!
答: 应该不会啊。pcap_next_ex 次捕获一个数据包存放在pkt_data里边,下次循环捕获数据包的时候pkt_data存放的原来的内容就被覆盖了的。我原来做的时候也没出现内存增长 过大的情况啊,我记得当时我的程序占的内存大约就是20M左右。
第四天
Winpcap 提供( libpcap 也提供)的一个强大特性是过滤引擎( filtering engine )。它提供了一个非常有效的接收网络流量的方法,并且它通常与 Winpcap 提供的抓包机制集成在一起。用于过滤数据包的函数是 pcap_complie() 和 pcap_setfilter() 。
pcap_complie() 使用一个包含高级布尔表达式的字符串并且产生一个能被过滤引擎集成到数据包驱动中的低级字节码。
pcap_setfilter() 把一个过滤器与核心驱动抓包会话关联起来。一旦 pcap_setfilter() 被调用,相关的过滤器将被应用到所有的来自网络的数据包上,并且所有的一致的数据包将被复制给应用程 序。
抓包和过滤
经过前面几天的知识准备,现在我们将把前面的知识综合后应用于一个简单的实际应用程序。下面试验的目的是如何解析和解释被捕获的数据包的协议头。应用程序 运行的结果是打印出一组我们的网络上的 UDP 通信数据。我们之所以选择解析和显示 UDP 协议是因为它比起其他协议(例如: TCP )更易于理解,对于初学者更适合。
试验代码4:
函数 1 :
int pcap_datalink(pcap_t* p)
返回链路层上的一个适配器。返回链路层的类型,链路层的类型包括:
l DLT_NULL : BSD 回路封装;链路层协议头是一个 4 字节的域,以主机字节顺序( host byte order ),包含一个从 socket.h 来的 PF_value 。主机字节顺序( host byte order )是捕获数据包的机器的字节顺序,而 PF_value 是捕获数据包的机器的 OS 。如果一个读取一个 文件,字节顺序和 PF_value 不一定是抓取文件的那些机器。
l DLT_EN10MB : 以太网( 10Mb, 100Mb, 1000Mb, 或 者更高)。
l DLT_IEEE802 : IEEE802.5 令牌环网。
l DLT_ARCNET : ARCNET 。
l DLT_SLIP : SLIP 。
l DLT_PPP : PPP ;如果第一个字节是 0xff 或 0x03 ,它是类 HDLC 帧上的 PPP 。
l DLT_FDDI : FDDI
l DLT_ATM_RFC1483 : RFC1483LLC/SNAP ATM ;数 据包以 IEEE802.2 LLC 头开始。
l DLT_RAW :原始 IP ( raw IP );数据包以 IP 头开始。
l DLT_PPP_SERIAL :按照 RFC1662 ,基于类 HDLC 帧的 PPP ,或者按照 RFC1547 的 4.3.1 ,基于 HDLC 帧的 Cisco PPP ;前者的第一个字节是 0xFF ,后者的第一个 字节是 0x0F 或 0x8F 。
l DLT_PPP_ETHER :按照 RFC2516 , PPPoE ;数据包以 PPPoE 头开始。
l DLT_C_HDLC :按照 RFC1547 的 4.3.1 ,基于 HDLC 帧的 Cisco PPP 。
l DLT_IEEE802_11 : IEEE 802.11 无线局域网。
l DLT_FRELAY :帧中继( Frame Relay )。
l DLT_LOOP : OpenBSD 回路封装。
l DLT_LINUX_SLL : Linux 抓包封装。
l DLT_LTALK :苹果的 LocalTalk ,数据包以 AppleTalk LLAP 头开始。
l DLT_PFLOG : OpenBSD pflog 。
l DLT_PRISM_HEADER :后接 802.11 头的棱镜监视器模式( Prism monitor mode )信息。
l DLT_IP_OVER_FC : RFC2625 IP-over-Fiber 频 道,以 RFC2625 中定义的 Network_Header 开始。
l DLT_SUNATM : SunATM 设备。
l DLT_IEEE802_11_RADIO :后接 802.11 头的链路层信息。
l DLT_ARCNET_LINUX :没有异常帧的 ARCNET 。
l DLT_LINUX_IRDA : Linux-IrDA 数据包, DLT_LINUX_SLL 头后接 IrLAP 头。
函数 2:
int pcap_compile(pcap_t* p,
struct bpf_program* fp,
char* str,
int optimize,
bpf_u_int32 netmask)
编译一个数据包过滤器,将一个能被核心态( kernel-level )过滤器引擎解释的程序中的高层过滤表达式( filtering expression )进行转化。 pcap_compile() 被用来将字符串 str 编译进过滤器程序( fp ),程序( fp )是一个指向 bpf_program 结 构体并被 pcap_compile() 赋值的指针。 optimize 控制是 否对目标代码( resulting code )的性能进行优化。 Netmask 表明 IPv4 掩码,它仅在检查过滤器程序中的 IPv4 广播地址的时候被使用。如果网络掩码对于程序是不可知的或者数据包是在 Linux 的 ” 任何( any ) ” 伪接口上被捕获的,则赋值 0 ; IPv4 广播地址的测试将不被正确进行,但过 滤器程序中的其他所有的测试都不会有问题。返回- 1 表示发生了错误,此时, pcap_geterr() 将被用来显示错误信息。
函数 3 :
int pcap_setfilter(pcap_t* p,
struct bpf_program* fp)
把一个过滤器同一次抓包关联起来。 pcap_setfilter 被用来指定一个过滤器程序。 fp 是一个指向 bpf_program 结构体的指针,通常是 pcap_compile() 执行的结果。当失败时返回- 1 ,此时, pcap_geterr() 被用来显示错误信息;返回 0 表示成功。
首先,首先我们设置过滤器为“ ip and udp ”。 用这个方法我们可以确保 packet_handler() 仅接收 IPv4 上的 UDP 数据包:这就简化了解析过程并且提高了程序的效率。
我们还创建了 IP 和 UDP 两个结构体,这些结构体被 packet_handler() 用来定位不同的头域。
packet_handler() 展现了像 tcpdump/WinDump 这些复杂的 嗅探器( sniffer )如何解析网络数据包。因为我们不关心 MAC 头,所以我们跳过它。为了简便起见,抓包前我们用 pcap_datalink() 检查 MAC 层,以确保我们处理的是以太网。该方法 可以确保 MAC 头是 14 字节。
IP 头的定位在 MAC 头定位之后。我们从 IP 包头中取出源 IP 地址和目标 IP 地址。
展开 UDP 包头有点复杂,因为 IP 包头不是定长的。因此,我们使用 IP 包头的长度域来获 得它的大小。一旦我们知道了 UDP 头的位置,我们可以取出源端口和目的端口。
精华评论
问: if (d->addresses != NULL) { /* Retrieve the mask of the first address of the interface */ netmask = ((struct sockaddr_in *)(d->addresses->netmask))->sin_addr.S_un.S_addr; }
else { /* If the interface is without addresses we suppose to be in a C class network */ netmask = 0xffffffff; } 这段代码的作用是什么
答: 就是将pcap_addr结构体中的netmask字段赋值给netmask变量。
if判断表示如果网卡d的addresses不为NULL,那么就将其中 的netmask转化成相应格式的数据并赋值给netmask;否则,就netmask变量赋值为ffffffff。
问: fprintf(stderr, "Error in pcap_findalldevs: %s/n", errbuf); 我搞晕了,
int fprintf(FILE *stream, char *format, <variable-list>); 中的不是要把内容写进FILE *stream里面吗?这个FILE *stream是一个文件还是一个缓冲区。如果是文件的话是已
存在的文件还是要创建的文件,还是都可以? 我不知道这里的stderr是一个文件,还是一个一缓冲区。是系统自带的还是用户自行定义的啊? 我第四天的代码编译能通过当他运行到if ((adhandle = pcap_open(d->name, /*name of the device */ 65536, PCAP_OPENFLAG_PROMISCUOUS, /* promiscuous mode */ 1000, /* read timeout */ NULL, /* remote authentication */ errbuf /* error buffer */ )) == NULL)
就会出错,我不知道是不是和stderr有关。如果stderr是文件它有没有固定放置的位置阿?
答: fprintf是C中的一个输入输出函数。它是把<variable-list>的值以format指定的格式输出到stream指定的文件 中。stream所指的文件是否需要存在你可以自己写一段代码试一下。 stderr是系统自动定义的标准出错输出(也就是从终端输出),另外还有stdin,stdout也是系统定义的,分别指向终端输入、终端输出。 这段代码我是编译过的,应该没有问题。可能是你编译器的问题。
再问: stderr是系统自动定义的标准出错输出(也就是从终端输出)。那就是说把Error in pcap_findalldevs加errbuf的内容输出到终端上,而fprintf是要放入文件的阿?那为什么这里不用printf而是要用 fprintf,难道说fprintf也能用来只输出到屏幕上吗?
答: 在UNIX操作系统结构中,它把各种外部设备也看成是文件。所以 fprintf不仅能把errbuf的内容输出到磁盘文件里,也可以输出到像终端这样的特殊文件里。printf是fprintf的一个特例,它固定的只 能把相应内容输出到终端文件上。你可以看下C语言里的文件操作就明白了。
再问: 终于发现运行出错的原因了! /* Jump to the selected adapter */ for (d = alldevs; d; d = d->next); 的 for (d = alldevs; d; d = d->next);这句话的指针出错了,指向了下一个了。我把它改成 for (d = alldevs, i = 0; i < inum - 1; d = d->next, ++ i);就对了。我就不知道你怎么不会出错呢
答: 因为alldevs相当于是个链表,对链表的每一个节点进行访问用前面那句就可以了;你的做法也对,不过还要先遍历一遍链表以获得链表的长度 (inum),效率较前者低。如果你的这种写法不会出错的话你用你的这种写法也行。
再问: 前面的程序里面有: printf("Enter the interface number (1 - %d):", i); scanf("%d", &inum); 这里的inum是输入的选择的是哪个 ,而不是链表的长度,链表的长度我认为是i. 我认为你的d也是链表的长度,才会出现错误,我曾经用for (d = alldevs; d; d = d->next);打印出来的地址是0。
答: 是的,是我错了。inum是表示网卡的编号。你是对的。
问: 在这段代码 if (pcap_datalink(adhandle) != DLT_EN10MB) { fprintf(stderr, "/nThis program works only on Ethernet networks./n");
/* Free the devices list */ pcap_freealldevs(alldevs); return -1; } 中“!= DLT_EN10MB”怎么又显示“This program works only on Ethernet networks.”是不是写错了呢? 二:在 if (d->addresses != NULL) { /* Retrieve the mask of the first address of the interface */ netmask = ((struct sockaddr_in *)(d->addresses->netmask))->sin_addr.S_un.S_addr; } else { /* If the interface is without addresses we suppose to be in a C class network */ netmask = 0xffffffff; }
中① netmask = ((struct sockaddr_in *)(d->addresses->netmask))->sin_addr.S_un.S_addr; 我不知道为什么要这么做,而且也不知道明白这段的语法。 ② 我也不知道为什么d->addresses = NULL就是c类网络? 三: uh = (udp_header*)((u_char*)ih + ip_len);不知道为什么要这样 四:pcap_setfilter被用来指定一个过滤器程序,首先我们设置过滤器为“ip and udp”没有看出哪段代码实现了这个功能
答: A1:DLT_EN10MB代表的是以太网,意思就是如果你选择的网卡不是以太网类型的网卡(或者说网络不是以太网),那么就提示错误,错误信息就是 “This program works only on Ethernet networks.”。
A2:看下pcap_if的定义也许对你有帮助: nstruct pcap_if { struct pcap_if *next; char *name; char *description; struct pcap_addr *addresses; u_int flags; };
A3:在c里面,对于一段连续的缓冲区,可以用强制转化的方式将其转化成一定的结构体类型。不过你要保证这段缓冲区中保存的数据确实跟你的结构体中的各个 字段是对应的,不然访问结构体的时候可能会出问题。
A4:pcap_setfilter我也没研究过,你可以参考下winpcap的文档看看。
"netmask = ((struct sockaddr_in *)(d->addresses->netmask))->sin_addr.S_un.S_addr;" d是一个pcap_if指针; d->addresses是引用pcap_if结构体中的addresses成员; d->addresses->netmask是引用pcap_addr结构体中的netmask成员; (struct sockaddr_in *)(d->addresses->netmask)是将netmask强制转化为sockaddr_in结构体; ((struct sockaddr_in)*)(d->addresses->netmask)->sin_addr是引用sockaddr_in结构 体中的成员sin_addr,sin_addr是一个struct in_addr结构体变量; ((struct sockaddr_in)*)(d->addresses->netmask)->sin_addr.S_un是引用in_addr结 构体中的S_un成员; ((struct sockaddr_in)*)(d->addresses->netmask)->sin_addr.S_un.S_addr是应用 S_un中的成员S_addr,S_addr是u_long类型的变量。
问: 你代码中计算长度有以下代码: ih = (ip_header*)(pkt_data + 14); ip_len = (ih->ver_ihl & 0xf) * 4; uh = (udp_header*)((u_char*)ih + ip_len); ih = (ip_header*)(pkt_data + 14); 这个我能明白,因为你在文中已经指出MAC头是14字节 但下面的2句 ip_len = (ih->ver_ihl & 0xf) * 4; uh = (udp_header*)((u_char*)ih + ip_len); 你虽然也有解释:“IP头的定位在MAC头定位之后。我们从IP包头中取出源IP地址和目标IP地址。 展开UDP包头有点复杂,因为IP包头不是定长的。因此,我们使用IP包头的长度域来获得它的大小。”但是我还是不明白,为什么要这么做,
答: 表示的是ip数据包头中的两个关键字段版本和报头长度。这两个字段每个都是4bit的,所以用一个字节来表示这两个字段:前四位 是版本字段,后四位是报头长度字段。而报头长度字段是以4个字节(32bit)为基本单位的,取值范围是5-15。 所以如果要取得ip报头长度(单位:字节)就是: ip_len = (ih->ver_ihl & 0xf) * 4; 解释:ih->ver_ihl & 0xf就是ih->ver_ihl与00001111按位与,这样就能取得ih->ver_ihl的后四位的值,也就是ip报头中的“报头长 度”字段,再将该按位与的结果乘以4(该字段以4个字节为基本单位),就得到ip报头有多少个字节。
再问: 再将该按位与的结果乘以4(该字段以4个字节为基本单位),就得到ip报头有多少个字节。 还是不太明白*4的道理
答: 相当于说ip头的第二个字段有4bit,表示报头长度,这个字段的单位是字(也就是四个字节),4bit能表示的范围是0-15,但ip报头最短都有20 字节,所以该字段的取值是5-15。 如果该字段是5,则表示报头长度是5个字,即20个字节;如果该字段是6,则表示报头长度是6个字,即24个字节,以此类推。(1个字=4个字节)
首部长度占四个字节,可表示的最大数值是15个单位(一个单位为四个字节,即31位),因为IP分组的首部长度必须为4个字节的整数倍,所以这个“单位” 表示四个字节。 当IP分组的首部长度不时4个字节的整数倍时,必须利用最后一个填充字段来填充,这样做的目的也就是为了实现IP协议是较为方便而制定的规则。 用(ih->ver_ihl & 0xf) 所得的结果就是上面所说的“单位数”,再乘以四就变成了整个的IP包头所占用的以字节为单位的长度。
rester(游客)发表评论于2006-3-20 11:08:10 我把// ip_len = (ih->ver_ihl & 0xf) * 4 改为ip_len=(ih->flags_fo & 0x1fff); uh = (udp_header*)((u_char*)ih + ip_len); 不知道这样可否得到预期的结果,(ih->flags_fo & 0x1fff)得到的是直接就是片偏移地址.