原来机器中的Winpcap的版本是3.0,程序总是运行不下去,分析之后得知原来是自己的Winpcap版本出了错,其中的一个函数pcap_findalldevs_ex(),在3.0的版本和3.2的版本中的参数的个数不一样,导致程序出错。事例代码如下:
#include <pcap.h>
#include <remote-ext.h>
/* 4 bytes IP address */
typedef struct ip_address
{
u_char byte1;
u_char byte2;
u_char byte3;
u_char byte4;
}ip_address;
/* IPv4 header */
typedef struct ip_header
{
u_char ver_ihl; /* Version (4 bits)版本 + Internet header length 首部长度(4 bits)*/
u_char tos; /* Type of service 服务类型*/
u_short tlen; /* Total length 16位标示*/
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 32位源IP地址*/
ip_address daddr;/* Destination address 32位目的IP地址*/
u_int op_pad; /* Option + Padding选项和数据 */
}ip_header;
/* UDP header */
typedef struct udp_header
{
u_short sport; /* Source port源端口 */
u_short dport; /* Destination port 目的端口*/
u_short len; /* Datagram length 数据长度*/
u_short crc; /* Checksum 校验和*/
}udp_header;
//连接表元素的结构
typedef struct link_elem
{
ip_address saddr; //IP地址Source ip address
ip_address daddr; //目标IP地址
u_short sport; // Source port
u_short dport; // Destinaion port
char *mailsaddr; //源Email地址
char *maildaddr; //目的Email地址
bool ifdata; //是否Data
time_t rtime; //到达时间
}link_elem;
//邮件正文段
typedef struct data_content
{
char *lcontent; //正文段
data_content *next_file; //下一个邮件正文段
}data_content;
/* Prototype of the 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];
u_int netmask;
char packet_filter[] = "ip and udp";
struct bpf_program fcode;???????????????????????//
/* Retrieve the device list */
if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &alldevs, errbuf) == -1)
{
/*
在UNIX操作系统结构中,它把各种外部设备也看成是文件。
所以 fprintf不仅能把errbuf的内容输出到磁盘文件里,
也可以输出到像终端这样的特殊文件里。printf是fprintf的一个特例,
它固定的只能把相应内容输出到终端文件上。
*/
fprintf(stderr, "Error in pcap_findalldevs: %s/n", errbuf);
exit(1);
}
/* Print the list*/
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");
/* Free the device list */
pcap_freealldevs(alldevs);
return -1;
}
/* Jump to the selected adapter */
for (d = alldevs, i = 0; i < inum - 1; d = d->next, ++ i);
/* Open the device */
if ((adhandle = pcap_open(d->name, /* 设备名 */
65536, /* 所截包的大小 */
/* 65536 guarantees that the whole packet will be captured on all the link layers */
PCAP_OPENFLAG_PROMISCUOUS, /* 混杂模式 */
1000, /* read timeout */
NULL, /* authentication on the remote machine远程机器的权限 */
errbuf /* error buffer */
)) == NULL)
{
fprintf(stderr, "/nUnable to open the adapter. %s is not supported by Winpcap/n");
/* Free the devices list */
pcap_freealldevs(alldevs);
return -1;
}
/* Check the link layer. We support only Ethernet for simplicity
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头。
*/
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;
}
/******************************************************
就是将pcap_addr结构体中的netmask字段赋值给netmask变量。
if判断表示如果网卡d的addresses不为NULL,
那么就将其中的netmask转化成相应格式的数据并赋值给netmask;
否则,就将netmask变量赋值为ffffffff。
********************************************************/
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;
}
/* complie the filter */
if (pcap_compile(adhandle, &fcode, packet_filter, 1, netmask) < 0)
{
fprintf(stderr, "/nUnable to compile the packet filter. Check the syntax./n");
/* Free the devices list */
pcap_freealldevs(alldevs);
return -1;
}
/* set the filter */
if (pcap_setfilter(adhandle, &fcode) < 0)
{
fprintf(stderr, "/nError setting the filter./n");
/* Free the devices list */
pcap_freealldevs(alldevs);
return -1;
}
printf("/nlistening on %s .../n", d->description);
/* At this point,we don't need any more the device list. Free it */
pcap_freealldevs(alldevs);
/* Start the capture */
pcap_loop(adhandle, 0, packet_handler, NULL);
return 1;
}
/* Callback function invoked by libpcap for every incoming packet */
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;
/* convert the timestamp to readable format */
ltime = localtime(&header->ts.tv_sec);
strftime(timestr, sizeof(timestr), "%H:%M:%S", ltime);
/* print timestamp and length of the packet */
printf("%s.%.6d len: %d ", timestr, header->ts.tv_usec, header->len);
/* retrieve the position of the ip header */
ih = (ip_header*)(pkt_data + 14); /* length of ethernet header MAC头是14字节*/
/* retrieve the position of the udp header */
/***********************************************
ih->ver_ihl & 0xf就是ih->ver_ihl与00001111按位与,
这样就能取得ih->ver_ihl的后四位的值,
也就是ip报头中的“报头长度”字段,
再将该按位与的结果乘以4(该字段以4个字节为基本单位),
就得到ip报头有多少个字节。
************************************************/
ip_len = (ih->ver_ihl & 0xf) * 4
/*IP包头的长度域来获得Ip包头的大小*/;
uh = (udp_header*)((u_char*)ih + ip_len);
/* convert from network byte order to host byte order */
/*sport = ntohs(uh->sport);
dport = ntohs(uh->dport);*/
/* print ip addresses and udp ports */
printf("%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*/);
}
#include <pcap.h>
#include <remote-ext.h>
/* 4 bytes IP address */
typedef struct ip_address
{
u_char byte1;
u_char byte2;
u_char byte3;
u_char byte4;
}ip_address;
/* IPv4 header */
typedef struct ip_header
{
u_char ver_ihl; /* Version (4 bits)版本 + Internet header length 首部长度(4 bits)*/
u_char tos; /* Type of service 服务类型*/
u_short tlen; /* Total length 16位标示*/
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 32位源IP地址*/
ip_address daddr;/* Destination address 32位目的IP地址*/
u_int op_pad; /* Option + Padding选项和数据 */
}ip_header;
/* UDP header */
typedef struct udp_header
{
u_short sport; /* Source port源端口 */
u_short dport; /* Destination port 目的端口*/
u_short len; /* Datagram length 数据长度*/
u_short crc; /* Checksum 校验和*/
}udp_header;
//连接表元素的结构
typedef struct link_elem
{
ip_address saddr; //IP地址Source ip address
ip_address daddr; //目标IP地址
u_short sport; // Source port
u_short dport; // Destinaion port
char *mailsaddr; //源Email地址
char *maildaddr; //目的Email地址
bool ifdata; //是否Data
time_t rtime; //到达时间
}link_elem;
//邮件正文段
typedef struct data_content
{
char *lcontent; //正文段
data_content *next_file; //下一个邮件正文段
}data_content;
/* Prototype of the 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];
u_int netmask;
char packet_filter[] = "ip and udp";
struct bpf_program fcode;???????????????????????//
/* Retrieve the device list */
if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &alldevs, errbuf) == -1)
{
/*
在UNIX操作系统结构中,它把各种外部设备也看成是文件。
所以 fprintf不仅能把errbuf的内容输出到磁盘文件里,
也可以输出到像终端这样的特殊文件里。printf是fprintf的一个特例,
它固定的只能把相应内容输出到终端文件上。
*/
fprintf(stderr, "Error in pcap_findalldevs: %s/n", errbuf);
exit(1);
}
/* Print the list*/
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");
/* Free the device list */
pcap_freealldevs(alldevs);
return -1;
}
/* Jump to the selected adapter */
for (d = alldevs, i = 0; i < inum - 1; d = d->next, ++ i);
/* Open the device */
if ((adhandle = pcap_open(d->name, /* 设备名 */
65536, /* 所截包的大小 */
/* 65536 guarantees that the whole packet will be captured on all the link layers */
PCAP_OPENFLAG_PROMISCUOUS, /* 混杂模式 */
1000, /* read timeout */
NULL, /* authentication on the remote machine远程机器的权限 */
errbuf /* error buffer */
)) == NULL)
{
fprintf(stderr, "/nUnable to open the adapter. %s is not supported by Winpcap/n");
/* Free the devices list */
pcap_freealldevs(alldevs);
return -1;
}
/* Check the link layer. We support only Ethernet for simplicity
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头。
*/
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;
}
/******************************************************
就是将pcap_addr结构体中的netmask字段赋值给netmask变量。
if判断表示如果网卡d的addresses不为NULL,
那么就将其中的netmask转化成相应格式的数据并赋值给netmask;
否则,就将netmask变量赋值为ffffffff。
********************************************************/
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;
}
/* complie the filter */
if (pcap_compile(adhandle, &fcode, packet_filter, 1, netmask) < 0)
{
fprintf(stderr, "/nUnable to compile the packet filter. Check the syntax./n");
/* Free the devices list */
pcap_freealldevs(alldevs);
return -1;
}
/* set the filter */
if (pcap_setfilter(adhandle, &fcode) < 0)
{
fprintf(stderr, "/nError setting the filter./n");
/* Free the devices list */
pcap_freealldevs(alldevs);
return -1;
}
printf("/nlistening on %s .../n", d->description);
/* At this point,we don't need any more the device list. Free it */
pcap_freealldevs(alldevs);
/* Start the capture */
pcap_loop(adhandle, 0, packet_handler, NULL);
return 1;
}
/* Callback function invoked by libpcap for every incoming packet */
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;
/* convert the timestamp to readable format */
ltime = localtime(&header->ts.tv_sec);
strftime(timestr, sizeof(timestr), "%H:%M:%S", ltime);
/* print timestamp and length of the packet */
printf("%s.%.6d len: %d ", timestr, header->ts.tv_usec, header->len);
/* retrieve the position of the ip header */
ih = (ip_header*)(pkt_data + 14); /* length of ethernet header MAC头是14字节*/
/* retrieve the position of the udp header */
/***********************************************
ih->ver_ihl & 0xf就是ih->ver_ihl与00001111按位与,
这样就能取得ih->ver_ihl的后四位的值,
也就是ip报头中的“报头长度”字段,
再将该按位与的结果乘以4(该字段以4个字节为基本单位),
就得到ip报头有多少个字节。
************************************************/
ip_len = (ih->ver_ihl & 0xf) * 4
/*IP包头的长度域来获得Ip包头的大小*/;
uh = (udp_header*)((u_char*)ih + ip_len);
/* convert from network byte order to host byte order */
/*sport = ntohs(uh->sport);
dport = ntohs(uh->dport);*/
/* print ip addresses and udp ports */
printf("%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*/);
}