My site
本文主要参考了以下资料:RaiChen的博客,WinPcap官方文档,《网络分析技术揭秘》-吕雪峰,Phinecos(洞庭散人)的博客
获取网络适配器基本信息后,我们继续来获取适配器的一些高级信息。
接下来,先看一段代码:
#include "pcap.h"
#include<cstdlib>
#include<iostream>
#ifndef WIN32
#include <sys/socket.h>
#include <netinet/in.h>
#else
#include <winsock.h>
#endif
//Function prototypes
void ifprint(pcap_if_t *d);
char *iptos(u_long in);
char *ip6tos(struct sockaddr *sockaddr,char *address, int addrlen);
int main()
{
pcap_if_t *alldevs;
pcap_if_t *d;
char errbuf[PCAP_ERRBUF_SIZE+1];
char source[PCAP_ERRBUF_SIZE+1];
printf("Enter the device you want to list:\n"
"rpcap://==>lists interfaces in the local machine\n"
"rpcap://hostname:port==>lists interfaces in a remote machine\n"
" (rpcapd damon must be up and running\n"
"file://foldername==>lists all pcap files in the give folder\n\n"
"Enter your choice: ");
fgets(source,PCAP_ERRBUF_SIZE,stdin);
std::cout<<"source:"<<source<<std::endl;
source[PCAP_ERRBUF_SIZE] = '\0';
//Retrieve the interfaces list
if(pcap_findalldevs_ex(source,NULL,&alldevs,errbuf) == -1)
{
fprintf(stderr,"Error in pcap_findalldevs: %s\n",errbuf);
exit(1);
}
//scan the list printing every entry
for(d=alldevs;d;d=d->next)
{
ifprint(d);
}
pcap_freealldevs(alldevs);
system("PAUSE");
return 1;
}
//Print all the available information on the given interface
void ifprint(pcap_if_t *d)
{
pcap_addr_t *a;
char ip6str[128];
//Name
printf("%s\n",d->name);
//Description
if(d->description)
printf("\tDescription:%s\n",d->description);
//Loopback Address
printf("\td->flags:%s\n",d->flags);
printf("\tLoopback: %s\n",(d->flags & PCAP_IF_LOOPBACK)?"yes":"no");
//IP addresses
for(a=d->addresses;a;a=a->next)
{
printf("\tAddress Family: #%d\n",a->addr->sa_family);
switch(a->addr->sa_family)
{
case AF_INET:
printf("\tAddress Family Name:AF_INET\n");
if(a->addr)
printf("\tAddress:%s\n",iptos(((struct sockaddr_in *)a->addr)->sin_addr.s_addr));
if(a->netmask)
printf("\tNetmask:%s\n",iptos(((struct sockaddr_in *)a->netmask)->sin_addr.s_addr));
if(a->broadaddr)
printf("\tBroadcast Address:%s\n",iptos(((struct sockaddr_in *)a->broadaddr)->sin_addr.s_addr));
if(a->dstaddr)
printf("\tDestination Address:%s\n",iptos(((struct sockaddr_in *)a->dstaddr)->sin_addr.s_addr));
break;
case AF_INET6:
printf("\tAddress Family Name:AF_INET6\n");
if(a->addr)
printf("\tAddress:%s\n",ip6tos(a->addr,ip6str,sizeof(ip6str)));
break;
default:
printf("\tAddress Family Name:Unknow\n");
break;
}
}
printf("\n");
}
//From tcptraceroute,convert a numeric IP address to a string
#define IPTOSBUFFERS 12
char *iptos(u_long in)
{
static char output[IPTOSBUFFERS][3*4+3+1];
static short which;
u_char *p;
p = (u_char *)∈
which = (which + 1 == IPTOSBUFFERS ? 0 : which +1);
_snprintf_s(output[which],sizeof(output[which]),sizeof(output[which]),"%d.%d.%d.%d",p[0],p[1],p[2],p[3]);
return output[which];
}
char *ip6tos(struct sockaddr *sockaddr,char *address,int addrlen)
{
socklen_t sockaddrlen;
#ifdef WIN32
sockaddrlen = sizeof(struct sockaddr_in6);
#else
sockaddrlen = sizeof(struct sockaddr_storage);
#endif
if(getnameinfo(sockaddr,
sockaddrlen,
address,
addrlen,
NULL,
0,
NI_NUMERICHOST) != 0) address = NULL;
return address;
}
首先,如果不是windows系统,则包含两个库文件:sys/socket.h与netinet/in.h,windows系统则包含winsock.h。
接下来定义了三个函数原型,ifprint用来打印适配器的高级信息,iptos与ip6tos分别用来将ipv4与ipv6地址转换为字符串,这些函数会在后面详细介绍。
之后便是主函数,也是先定义两个pcap_if_t指针,指向适配器。字符数组errbuf用来存储错误信息。source用来指定读取数据包的“源头”,根据WinPcap语法规定,source为‘rpcap://’时表示读取本地网络适配器的数据包信息,为‘rpcap://host:port’表示读取名为host的远程主机的port端口上的数据包信息,为‘file://c:/myfolder/’表示读取C盘下myfolder文件夹内pcap文件的数据包信息。
之后提示用户输入想要读取数据包的方式,存入source中,然后和上节一样,调用pcap_findalldevs_ex()函数获取本机适配器列表,然后调用ifprint()函数循环输出每一个适配器的详细信息,最后用pcap_freealldevs()函数释放适配器列表,接下来详细介绍ifprint()函数的内容。
ifprint()函数接受一个pcap_if_t类型的参数,读取各个适配器的名称、描述和地址。函数开头定义了一个类型为pcap_addr_t的指针,pcap_addr_t其实是一个pcap_addr的结构体,用来保存适配器的各种地址,定义如下:
struct pcap_addr {
struct pcap_addr *next;
struct sockaddr *addr;
struct sockaddr *netmask;
struct sockaddr *broadaddr;
struct sockaddr *dstaddr;
};
其中,next用来指向列表的下一个元素,如果为最后一个元素则next为空。后面四个都是sockaddr类型的指针变量,sockaddr是socket用来处理网络地址的结构体,还有一些其他用来处理网络地址的结构体定义如下:
include <netinet/in.h>
// All pointers to socket address structures are often cast to pointers
// to this type before use in various functions and system calls:
struct sockaddr {
unsigned short sa_family; // address family, AF_xxx
char sa_data[14]; // 14 bytes of protocol address
};
// IPv4 AF_INET sockets:
struct sockaddr_in {
short sin_family; // e.g. AF_INET, AF_INET6
unsigned short sin_port; // e.g. htons(3490)
struct in_addr sin_addr; // see struct in_addr, below
char sin_zero[8]; // zero this if you want to
};
struct in_addr {
unsigned long s_addr; // load with inet_pton()
};
// IPv6 AF_INET6 sockets:
struct sockaddr_in6 {
u_int16_t sin6_family; // address family, AF_INET6
u_int16_t sin6_port; // port number, Network Byte Order
u_int32_t sin6_flowinfo; // IPv6 flow information
struct in6_addr sin6_addr; // IPv6 address
u_int32_t sin6_scope_id; // Scope ID
};
struct in6_addr {
unsigned char s6_addr[16]; // load with inet_pton()
};
// General socket address holding structure, big enough to hold either
// struct sockaddr_in or struct sockaddr_in6 data:
struct sockaddr_storage {
sa_family_t ss_family; // address family
// all this is padding, implementation specific, ignore it:
char __ss_pad1[_SS_PAD1SIZE];
int64_t __ss_align;
char __ss_pad2[_SS_PAD2SIZE];
};
这些struct的资料来自Beej’s Guide to Network Programming
其中带有family的变量表示地址簇,通常为AF_INET(表示IPv4地址簇)或AF_INET6(表示IPv6地址簇),其他就是一些端口、地址等的信息。
在pcap_addr结构体中,addr表示适配器的网络地址,netmask表示addr地址对应的掩码,broadaddr表示addr对应的广播地址,dstaddr表示addr对应的目的地址。在后面这些成员变量都会用到。
介绍完pcap_addr_t结构体后,定义了一个128字节的char数组,用来存储ipv6地址。接下来用d->name和d->description分别打印适配器的名称与描述,d->flags打印标志信息,用flags与PCAP_IF_LOOPBACK按位与来确定适配器是否是环回适配器。
接下来用一个for循环读取d->addresses里的适配器地址信息,a=a->next指向下一个地址信息,a为空时结束循环。循环体内,首先打印地址簇信息,如果是ipv4地址簇,则分别打印ipv4地址、掩码、广播地址和目的地址,如果是ipv6则打印ipv6地址:
for(a=d->addresses;a;a=a->next)
{
printf("\tAddress Family: #%d\n",a->addr->sa_family);
switch(a->addr->sa_family)
{
case AF_INET:
printf("\tAddress Family Name:AF_INET\n");
if(a->addr)
printf("\tAddress:%s\n",iptos(((struct sockaddr_in *)a->addr)->sin_addr.s_addr));
if(a->netmask)
printf("\tNetmask:%s\n",iptos(((struct sockaddr_in *)a->netmask)->sin_addr.s_addr));
if(a->broadaddr)
printf("\tBroadcast Address:%s\n",iptos(((struct sockaddr_in *)a->broadaddr)->sin_addr.s_addr));
if(a->dstaddr)
printf("\tDestination Address:%s\n",iptos(((struct sockaddr_in *)a->dstaddr)->sin_addr.s_addr));
break;
case AF_INET6:
printf("\tAddress Family Name:AF_INET6\n");
if(a->addr)
printf("\tAddress:%s\n",ip6tos(a->addr,ip6str,sizeof(ip6str)));
break;
default:
printf("\tAddress Family Name:Unknow\n");
break;
}
}
其中,打印ipv4地址时需要将sockaddr结构强制转换为sockaddr_in结构,读取到地址信息后再通过iptos()函数转换为常见的点分十进制表示,其中sockaddr_in结构体的定义上面已经给出。打印ipv6地址时,需通过ip6tos()函数转换为ipv6地址表示,存入ip6str字符数组后打印出来。
iptos()函数通过_snprintf_s()函数将ipv4地址转换为点分十进制表示,ip6tos()函数通过getnameinfo()函数将ipv6地址转换为主机名。
运行程序,在我的计算机上得到如下结果:
获取了适配器的高级信息后,我们终于可以开始捕获数据包了!!!下一篇:打开适配器并捕获数据包