6.2 获得网络适配器列表的幕后
从应用空间到内核空间获得网络适配器列表的重要函数的调用关系如图6-2所示。
图 6-2 函数调用关系图
6.2.1 wpcap.dll库中获得网络适配器列表的实现
1.pcap_findalldevs_ex函数
pcap_findalldevs_ex函数在获得网络适配器设备的列表时,除了可以列出在本机上的网络设备外,还可以列出一个远程机器上的网络设备和一个给定文件夹中可用的pcap格式的文件,其原型如下:
int pcap_fndalldevs_ex(char*source,
struct pcap_rmtauthauth,pcap_if_t**alldevs,charerrbuf);
上述函数中,参数source用于为函数明确查找设备的地址,它是一个字符型的缓冲区,依据新的WinPcap语法(参见附录A)保存着“源的位置”。检查该源以寻找适配器或pcap文件。
参数auth是一个指向pcap_rmtauth结构体的指针。该指针保持着认证RPCAP连接到远程主机所需的信息。该参数对本地主机请求没什么意义时,可设为NULL。
参数alldevs是一个pcap_if_t结构体类型的指针,在pcap_findalldevs_ex函数分配。函数成功返回时,该指针被设置为指向网络设备列表的第一个元素,此列表的每个元素都是pcap_if_t类型的。
参数errbuf是一个指向为用户分配的缓冲区(大小为PCAP_ERRBUF_SIZE)的指针,如果函数操作出现错误,该缓冲区将存储相应错误信息。函数操作出现错误的可能原因如下:
❑WinPcap没有安装在本地/远程主机上。
❑用户没有足够的权限来列出这些设备/文件。
❑网络故障。
❑RPCAP版本协商失败。
❑其他错误(如没足够的内存)。
函数操作成功返回0,否则返回-1。参数alldevs用于返回设备列表,当函数正确返回时,alldevs不能为NULL。当系统没有任何接口时,该函数返回-1。
值得注意的是,通过调用pcap_findalldevs_ex函数获得的网络设备可能存在不能被pcap_open函数打开的现象(如没有足够的权限来打开某些网络设备并对其进行捕获),此时,这些设备将不会出现在设备列表中。
pcap_findalldevs_ex函数的主要实现代码如下:
int pcap_fndalldevs_ex(char*source,
struct pcap_rmtauthauth,pcap_if_t**alldevs,charerrbuf)
{
……
char tmpstring[PCAP_BUF_SIZE+1];
pcap_if_t*dev;
……
/检查源source的类型(文件、本地主机、远程主机)/
if(pcap_parsesrcstr(source,&type,NULL,NULL,NULL,errbuf)==-1)
return-1;
/源为本地主机/
if(type==PCAP_SRC_IFLOCAL)
{
if(pcap_parsesrcstr(source,&type,host,NULL,NULL,errbuf)==-1)
{
return-1;
}
tmpstring[PCAP_BUF_SIZE]=0;
//**从本地主机获得网络适配器列表
if(pcap_fndalldevs(alldevs,errbuf)==-1)
return-1;
if((alldevs==NULL)||(*alldevs==NULL))
{//没找到适配器,确认WinPcap在本地主机上是否正确安装
return-1;
}
//**扫描所有这些适配器并修改适配器的名称与描述
//这是一个技巧,为了防止在此处重新执行pcap_findalldevs
dev=*alldevs;
while(dev)
{
//**创建、复制设备名称
//创建新的设备名称
if(pcap_createsrcstr(tmpstring,PCAP_SRC_IFLOCAL,
NULL,NULL,dev->name,errbuf)==-1)
return-1;
//释放旧内存,分配新内存
free(dev->name);
dev->name=(char*)malloc(strlen(tmpstring)+1);
……
//处理分配失败
//把新的设备名称复制到正确的内存区
strncpy(dev->name,tmpstring,strlen(tmpstring)+1);
//**创建、复制设备描述
//创建新的设备描述
if((dev->description==NULL)||(dev->description[0]==0))
{
snprintf(tmpstring,sizeof(tmpstring)-1,
"%s'%s'%s",PCAP_TEXT_SOURCE_ADAPTER,
dev->name,PCAP_TEXT_SOURCE_ON_LOCAL_HOST);
}
else
{
snprintf(tmpstring,sizeof(tmpstring)-1,
"%s'%s'%s",PCAP_TEXT_SOURCE_ADAPTER,
dev->description,PCAP_TEXT_SOURCE_ON_LOCAL_HOST);
}
//释放旧内存,分配新内存
free(dev->description);
dev->description=(char*)malloc(strlen(tmpstring)+1);
……
//处理分配失败
//把新的设备描述复制到正确的内存区
strncpy(dev->description,tmpstring,strlen(tmpstring)+1);
//**指向下一个结点
dev=dev->next;
}
return 0;
}
(*alldevs)=NULL;
/源为文件/
if(type==PCAP_SRC_FILE)
{
……
return 0;
}
/如果程序执行到此处,表明源为远程主机/
……
}
pcap_findalldevs_ex函数可创建一个能用pcap_open函数打开的网络适配器设备列表,其是pcap_findalldevs函数的一个扩展。
2.pcap_findalldevs函数
pcap_findalldevs函数用于获得已连接并能打开的所有网络适配器列表,不过它只允许列出本机上的网络设备,而pcap_findalldevs_ex函数可以列出一个远程机器上的网络设备,而且还能列出一个给定文件夹中可用的pcap格式的文件。由于pcap_findalldevs_ex函数的实现依靠标准的pcap_findalldevs函数来获得本地机器的网络适配器列表,因此它是平台无关的。
pcap_findalldevs函数的原型如下:
int pcap_fndalldevs(pcap_if_t*alldevsp,charerrbuf);
上述函数中,参数alldevsp指向列表的第一个元素,列表的每个元素都为pcap_if_t类型。如果没有已连接并能打开的网络适配器,则表示该列表可能为NULL。函数调用成功返回0,失败返回-1,参数errbuf用于存储错误信息。
值得注意的是,通过pcap_findalldevs函数获得的网络适配器可能不能被pcap_open_live函数打开(如没有足够的权限来打开它们并进行捕获),此时,这些设备将不出现在设备列表中。
pcap_findalldevs函数的主要实现步骤如图6-3所示。
图 6-3 pcap_findalldevs函数的主要实现步骤
pcap_findalldevs函数的主要实现代码如下:
int pcap_fndalldevs(pcap_if_t*alldevsp,charerrbuf)
{
pcap_if_t*devlist=NULL;
int ret=0;
const char*desc;
char*AdaptersName;
ULONG NameLength;
char*name;
/获取存储适配器列表所需的字节数NameLength,并分配存储适配器列表所需的内存/
if(!PacketGetAdapterNames(NULL,&NameLength))
{
//获取字节数失败,处理错误,程序退出
……
}
if(NameLength>0)
{
AdaptersName=(char*)malloc(NameLength);
}
else
{
*alldevsp=NULL;
return 0;
}
if(AdaptersName==NULL)
{//分配内存失败
return(-1);
}
/获取可用网络适配器列表/
if(!PacketGetAdapterNames(AdaptersName,&NameLength))
{
snprintf(errbuf,PCAP_ERRBUF_SIZE,
"PacketGetAdapterNames:%s",pcap_win32strerror());
free(AdaptersName);
return(-1);
}
/为发现的每个适配器在devlist链表中添加一个结点/
//函数PacketGetAdapterNames返回一个以空字符结尾的ASCII字符串,
//该字符串存储适配器的名字列表,以一个空字符结尾,
//其后为一个以空字符结尾的ASCII字符串,该字符串存储适配器的描述列表,
//以一个空字符结尾。这意味着在第一个列表的末尾有两个ASCII码格式的空字符。
//查找第一个列表的末尾,那是第二个列表的开始
//查找第一个紧相连的两个空字符('\0\0')
desc=&AdaptersName[0];
while(desc!='\0'||(desc+1)!='\0')
desc++;
//找到"desc"指向适配器名列表末尾的两个空字符的第一个空字符
//因此描述列表的第一个字节在后面两个字节的位置上
desc+=2;
//循环遍历第一个列表中的所有元素
name=&AdaptersName[0];
while(*name!='\0'){
//为每个接口在devlist链表中添加一个结点
if(pcap_add_if_win32(&devlist,name,desc,errbuf)==-1)
{//操作失败
ret=-1;
break;
}
name+=strlen(name)+1;
desc+=strlen(desc)+1;
}
if(ret!=-1)
{
/至此,没有任何错误,做任何特定于平台的操作,添加设备/
/*pcap_platform_finddevs是pcap_findalldevs的内部接口,
*pcap_platform_finddevs是一个依赖平台的函数,
*添加没有被“标准”机制(SIOCGIFCONF,getifaddrs等)找到的设备
*/
if(pcap_platform_fnddevs(&devlist,errbuf)<0)
ret=-1;
}
/发生错误,释放所构建的设备列表devlist/
if(ret==-1)
{
if(devlist!=NULL){
pcap_freealldevs(devlist);
devlist=NULL;
}
}
/返回最终的适配器列表/
*alldevsp=devlist;
free(AdaptersName);//释放存储适配器列表的内存
return(ret);
}
pcap_findalldevs函数首先调用Packet.dll中提供的PacketGetAdapterNames函数,通过给第一个参数pStr传递NULL值,使第二个参数BufferSize返回存储适配器列表所需的字节数。程序按该字节数来分配内存空间给AdaptersName以存储适配器列表。把第一个参数设为AdaptersName所指的内存空间,第二次调用PacketGetAdapterNames函数,以获得适配器列表。
对所获的适配器列表进行解析,获得每个适配器的名称与描述,并调用pcap_add_if_win32函数把每个适配器的信息添加到适配器设备列表devlist中。为防止通过“标准”方式没有找到的设备被遗漏,可调用依赖平台的pcap_platform_finddevs函数查找余下的设备信息,并添加到适配器设备列表devlist中。
添加信息结束后,把devlist存放到alldevsp参数所指的内存中,释放存储适配器列表的AdaptersName所指的内存空间,函数返回。
(1)pcap_add_if_win32函数
pcap_add_if_win32函数用于为适配器在devlist列表中添加一个结点,其原型如下:
static int pcap_add_if_win32(pcap_if_t**devlist,
charname,const chardesc,char*errbuf)
上述函数中,参数devlist返回存储所有适配器详细信息的列表;参数name为适配器名称;参数desc为该适配器的描述;参数errbuf返回错误信息。
函数调用成功,返回0值,否则返回非0值。
pcap_add_if_win32函数的主要实现步骤如图6-4所示。
图 6-4 pcap_add_if_win32函数的主要实现步骤
pcap_add_if_Win32函数的主要实现代码如下:
static int pcap_add_if_win32(pcap_if_t*devlist,charname,
const chardesc,charerrbuf)
{
pcap_if_t*curdev;
npf_if_addr if_addrs[MAX_NETWORK_ADDRESSES];
LONG if_addr_size;
int res=0;
if_addr_size=MAX_NETWORK_ADDRESSES;
/给devlist添加该适配器结点,不带网络地址信息,curdev返回该适配器结点/
if(add_or_fnd_if(&curdev,devlist,name,0,desc,errbuf)
==-1)
{
//添加失败,函数返回
return(-1);
}
/调用PacketGetNetInfoEx函数,返回一个适配器的所有网络地址信息/
if(!PacketGetNetInfoEx((void*)name,if_addrs,
&if_addr_size))
{
//失败,此处并不返回失败,而是返回一个空的地址列表
//对于NdisWan适配器,这种情况可能发生,同时希望提供这些适配器列表,
//即使不能提供适配器的网络地址
return(0);
}
/现在向结点中添加网络地址信息列表/
//"curdev"表示该适配器结点,对该适配器添加它的网络地址列表
while(if_addr_size—>0)
{
if(curdev==NULL)
break;
//把适配器的各种地址信息添加到curdev结点中
res=add_addr_to_list(curdev,
(struct sockaddr*)&if_addrs[if_addr_size].IPAddress,
(struct sockaddr*)&if_addrs[if_addr_size].SubnetMask,
(struct sockaddr*)&if_addrs[if_addr_size].Broadcast,
NULL,errbuf);
if(res==-1)
{//失败
break;
}
}
return(res);
}
此函数首先调用add_or_find_if函数给列表devlist添加适配器结点,但不带网络地址信息,curdev局部变量指向该结点。然后它会调用Packet.dll库提供的PacketGetNetInfoEx函数,返回该适配器存储的所有网络地址信息的列表。最后循环调用add_addr_to_list函数,向该结点添加网络地址信息。
add_addr_to_list函数会给一个适配器pcap_if_t类型的结点添加网络地址信息,其原型如下:
static int add_addr_to_list(pcap_if_t*curdev,
struct sockaddraddr,struct sockaddrnetmask,
struct sockaddrbroadaddr,struct sockaddrdstaddr,
char*errbuf)
上述函数中,参数curdev用于描述一个适配器详细信息列表的结点;参数addr、netmask、broadaddr、dstaddr分别为该适配器的IP地址、网络掩码、广播地址、P2P目的地址;参数errbuf返回该函数的错误信息。
函数调用成功,返回0,否则返回-1。
(2)add_or_find_if函数
add_or_find_if函数用于在alldevs设备列表中查找或添加一个结点,并返回该结点的信息,其原型如下:
int add_or_fnd_if(pcap_if_tcurdev_ret,pcap_if_talldevs,
const charname,u_int fags,const chardescription,
char*errbuf)
上述函数中,参数alldevs为描述所有适配器的列表;参数curdev_ret为所找到或添加的结点;参数name是适配器的名字;参数flag表示适配器的标识,比如是否支持回环功能;参数description是适配器的描述;参数errbuf返回函数的错误信息。
函数调用成功,返回0值,否则返回非0值。
add_or_find_if函数的主要实现步骤如图6-5所示。
图 6-5 add_or_find_if函数的主要实现步骤
add_or_find_if函数的主要实现代码如下:
int add_or_fnd_if(pcap_if_tcurdev_ret,pcap_if_talldevs,
const charname,u_int fags,const chardescription,char*errbuf)
{
pcap_t*p;
pcap_if_tcurdev,prevdev,*nextdev;
int this_instance;
/查找在设备列表中是否存在该适配器/
for(curdev=*alldevs;curdev!=NULL;curdev=curdev->next)
{
if(strcmp(name,curdev->name)==0)
break;//找到了该适配器,跳出for循环
}
if(curdev==NULL)
{
//没有找到,我们能打开该适配器实现在线捕获吗?
p=pcap_open_live(name,68,0,0,errbuf);
if(p==NULL)
{//即使不能打开,也不把此当作一个错误
*curdev_ret=NULL;
return(0);
}
pcap_close(p);
//可以成功地打开该适配器,为它分配一个新的适配器结点
curdev=malloc(sizeof(pcap_if_t));
if(curdev==NULL)
{//分配失败,返回
return(-1);
}
//填充该结点,除了网络地址信息
curdev->next=NULL;
//设置适配器的名字
curdev->name=strdup(name);
……//处理错误的代码
//设置适配器的描述
if(description!=NULL)
{//有适配器的描述
curdev->description=strdup(description);
……//处理错误的代码
}
else
{
//没有适配器的描述
curdev->description=NULL;
}
//设置适配器的网络地址列表,设置为NULL
curdev->addresses=NULL;
//设置PCAP_IF_XXX标志
curdev->fags=0;
if(ISLOOPBACK(name,fags))
curdev->fags|=PCAP_IF_LOOPBACK;
//把结点添加到合适的位置
this_instance=get_instance(name);
//首先,获取适配器的实例数
//现在寻找最后一个实例号小于等于该新适配器实例号的适配器,
//除了非环回适配器,因为环回适配器放置在列表的尾部
prevdev=NULL;
for(;;){
//获得该实例后的适配器
if(prevdev==NULL){
//下一个元素就是第一个元素
nextdev=*alldevs;
}else
nextdev=prevdev->next;
//处于列表的尾部吗?
if(nextdev==NULL){
//是的
break;
}
//判断该新适配器是否是一个非环回适配器,并且下一个适配
//器是否是一个环回适配器
if(!(curdev->fags&PCAP_IF_LOOPBACK)&&
(nextdev->fags&PCAP_IF_LOOPBACK))
{//是的,我们应该把新的结点插在"nextdev"之前,也就是"prevdev"之后
break;
}
//判断新适配器的实例号是否低于下一个适配器,
//且新适配器是否是一个非环回适配器,或新适配器是否为一个环回适配器
//两个环回检测的目的,
//是确认不把一个环回适配器放置在任何非环回适配器前,
//并且总是把非环回适配器放置在所有的环回适配器前
if(this_instance<get_instance(nextdev->name)&&
(!(curdev->fags&PCAP_IF_LOOPBACK)||
(nextdev->fags&PCAP_IF_LOOPBACK)))
{
//判断条件符合,应该把新结点放置在
//"nextdev"之前,
//也就是说放在"prevdev"之后
break;
}
prevdev=nextdev;
}
//在"nextdev"之前插入
curdev->next=nextdev;
//在"nextdev"之后插入,除非"prevdev"为空,
//在此情况下,这是第一个适配器
if(prevdev==NULL)
{
//这是第一个适配器,把它的指针传回去,并把"curdev"放
//置在"nextdev"之前
*alldevs=curdev;
}else
prevdev->next=curdev;
}
*curdev_ret=curdev;
return(0);
}
add_or_find_if函数调用get_instance函数获得适配器的实例号。get_instance函数原型如下:
static int get_instance(const char*name)
函数从参数name中分析适配器的实例号,如果name为any就返回INT_MAX,其是最大的整数值。