[网络分析] 2 打开和关闭适配器

该系列文章是依据本人平时对网络分析技术的学习,归纳总结,所做的学习笔记。如有错误或待改善之处,请留下您宝贵的意见或建议。

 

这一节,介绍适配器的打开和关闭,最后附上一个捕获数据包的代码。

 

相关函数描述:


1. Pacp_open() 函数用于打开一个通用的数据捕获源(包括本地主机、远程主机、文件三种类型),用于之后的数据包捕获与发送,原型如下:

pcap_t* pcap_open  ( const char *  source,int  snaplen,int  flags,int  read_timeout,
                    struct pcap_rmtauth *  auth,char *  errbuf)

Snaplen:制定要捕获数据包中的哪些部分。在一些操作系统中 (比如 xBSD 和 Win32),驱动可以被配置成只捕获数据包的初始化部分:这样可以减少应用程序间复制数据的量,从而提高捕获效率。本例中,我们将值定为65535,它比我们能遇到的最大的MTU还要大。因此,我们确信我们总能收到完整的数据包。

 

Flags:最最重要的flag是用来指示适配器是否要被设置成混杂模式。一般情况下,适配器只接收发给它自己的数据包,而那些在其他机器之间通讯的数据包,将会被丢弃。相反,如果适配器是混杂模式,那么不管这个数据包是不是发给我的,我都会去捕获。也就是说,我会去捕获所有的数据包。这意味着在一个共享媒介(比如总线型以太网),WinPcap能捕获其他主机的所有的数据包。大多数用于数据捕获的应用程序都会将适配器设置成混杂模式,所以,我们也会在下面的范例中,使用混杂模式。

 

to_ms:指定读取数据的超时时间,以毫秒计(1s=1000ms)。在适配器上进行读取操作(比如用 pcap_dispatch() 或 pcap_next_ex()) 都会在 to_ms 毫秒时间内响应,即使在网络上没有可用的数据包。在统计模式下,to_ms 还可以用来定义统计的时间间隔。将 to_ms 设置为0意味着没有超时,那么如果没有数据包到达的话,读操作将永远不会返回。如果设置成-1,则情况恰好相反,无论有没有数据包到达,读操作都会立即返回。


2. pcap_loop()函数,用于接收一组数据包,原型如下:

int pcap_loop(pcap_t *p,int cnt,pcap_handler callback,u_char *user)

3. pcap_handler()函数,当调用pcap_loop()或pcap_dispatch()函数时,数据包通过该回调函数传递给应用程序,其原型如下:

typedef void(*) pcap_handler(u_char *user,const struct pcap_pkthdr *pkt_header,const u_char *pkt_data)

user:是一个用户定义的参数,包含捕获回话的状态,其对应于pcap_disptch函数与pcap_loop函数的user参数;

pkt_header:是NPF驱动程序给数据包附加的一个信息头,而不是一个协议头;

pkt_data:指向数据包的数据,包括协议头。


4. pcap_close()函数用于关闭Pacp_open() 函数所打开的相关资源,原型如下:

void pcap_close(pcap_t *p)

相关结构体描述:


1.       pcap_t 结构体:

typedef struct pcap pcap_t;
struct pcap{
	ADAPTER *adapter;
	LPPACKET Packet;
	int nonblock;
	
	int snapshot;	
	int linktype;			//网络数据链路层的类型
	int linktype_ext;			//linktype成员的扩展
	int tzoff;				//时区偏移
	int offset;				//正确对其的偏移
	int activated;			//捕获是否已经准备好的标识,准备OK则为TRUE
	int oldstyle;			//如果使用pcap_open_live函数则设置为TRUE
	
	int break_loop;			//用以强制从数据包循环中读取推出的标识
	
	struct pcap_sf sf;
	struct pcap_md md;
	struct pcap_opt opt;
	
	//读取缓冲区
	int bufsize;
	u_char *buffer;
	u_char *bp;
	int cc;
	
	//pcap_next的位置持有者
	u_char *pkt;
	
	//接收数据的方向
	pcap_direction_t direction;
	
	//定义的各种方法
	activate_op_t activate_op;
	can_set_rfmon_op_t can_set_rfmon_op;
	read_op_t read_op;
	inject_op_t inject_op;
	setfilter_op_t setfilter_op;
	set_datalink_op_t set_datalink_op;
	getnonblock_op_t getnonblock_op;
	setnonblock_op_t setnonblock_op;
	stats_op_t stats_op;
	
	setbuff_op_t setbuff_op;
	setmode_op_t setmode_op;
	setmintocopy_op_t setmintocopy_op;
	
	cleanup_op_t cleanup_op;
	
	//如果数据包过滤不在内核中,在此放置过滤器代码
	struct bpf_program fcode;
	
	char errbuf[PCAP_ERRBUF_SIZE + 1];
	int dlt_count;
	u_int *dlt_list;
	
	//pcap_next_ex工作所需的链表
	struct pcap_pkthdr pcap_header;
}

代码:


打开一个适配器并捕获相应的数据包,打印每个数据包的长度和捕获时间。
#define WIN32
#define HAVE_REMOTE

#include <stdio.h>
#include "pcap.h"

#include <winsock.h>

/* 捕获数据包(packet handler)回调函数的原型*/
void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data);

int main()
{
	pcap_if_t *alldevs;
	pcap_if_t *d;
	int inum;
	int i=0;
	pcap_t *adhandle;
	char errbuf[PCAP_ERRBUF_SIZE];
    
    /* 获取本机网络设备列表 */
    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);
    
    /* 开始捕获 */
    pcap_loop(adhandle, 0, packet_handler, NULL);

    /*关闭设备*/
    pcap_close(adhandle);    
    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];
    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\n", timestr, header->ts.tv_usec, header->len);    
}

注意:


当适配器被打开,捕获工作就可以用pcap_dispatch() pcap_loop()进行。这两个函数非常的相似,区别就是pcap_ dispatch()当超时时间到了(timeout expires)就返回 (尽管不能保证),而 pcap_loop()不会因此而返回,只有当cnt数据包被捕获,所以,pcap_loop()会在一小段时间内,阻塞网络的利用。pcap_loop()对于我们这个简单的范例来说,可以满足需求,不过,pcap_dispatch()函数一般用于比较复杂的程序中。

 

这两个函数都有一个回调参数, packet_handler指向一个可以接收数据包的函数。这个函数会在收到每个新的数据包并收到一个通用状态时被libpcap所调用 ( 与函数 pcap_loop() pcap_dispatch()中的user参数相似),数据包的首部一般有一些诸如时间戳,数据包长度的信息,还有包含了协议首部的实际数据。注意:冗余校验码CRC不再支持,因为帧到达适配器,并经过校验确认以后,适配器就会将CRC删除,与此同时,大部分适配器会直接丢弃CRC错误的数据包,所以,WinPcap没法捕获到它们。

 

上面的程序将每一个数据包的时间戳和长度从pcap_pkthdr的首部解析出来,并打印在屏幕上。

 

请注意,使用pcap_loop()函数可能会遇到障碍,主要因为它直接由数据包捕获驱动所调用。因此,用户程序是不能直接控制它的。另一个实现方法(也是提高可读性的方法),是使用 pcap_next_ex() 函数。有关这个函数的使用,我们将在下一讲为您展示。 (不用回调方法捕获数据包).



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值