记录一下网络编程中获取当前本机网络配置的一些函数。
获取网络配置参数
GetNetworkParams
https://docs.microsoft.com/zh-cn/windows/win32/api/iphlpapi/nf-iphlpapi-getnetworkparams
定义如下:
IPHLPAPI_DLL_LINKAGE DWORD GetNetworkParams(
[out] PFIXED_INFO pFixedInfo,
[in] PULONG pOutBufLen
);
参数①
https://docs.microsoft.com/en-us/windows/win32/api/iptypes/ns-iptypes-fixed_info_w2ksp1
typedef struct {
char HostName[MAX_HOSTNAME_LEN + 4];
char DomainName[MAX_DOMAIN_NAME_LEN + 4];
PIP_ADDR_STRING CurrentDnsServer;
IP_ADDR_STRING DnsServerList;
UINT NodeType;
char ScopeId[MAX_SCOPE_ID_LEN + 4];
UINT EnableRouting;
UINT EnableProxy;
UINT EnableDns;
} FIXED_INFO_W2KSP1, *PFIXED_INFO_W2KSP1;
NodeType有四种:
BROADCAST_NODETYPE:广播节点
1、通过NetBIOS缓存进行查询,如果成功,返回一个IP地址,完成解析:如果不成功,转向下一步;
2、通过广播进行查询,如果成功,返回一个IP地址,完成解析;如果不成功,转向下一步;
3、通过本地HOSTS文件进行查询,无论成功与否都结束解析。
PEER_TO_PEER_NODETYPE:点到点
通过点对点通信从NetBIOS服务器查询以进行地址翻译,然后再直接将信息发送到目的计算机。
MIXED_NODETYPE:先广播再点到点
HYBRID_NODETYPE:先点到点再广播
其实还有一种UNKNOWN未知类型。
其他参数不解释,看代码:
#include <winsock2.h>
#include <iphlpapi.h>//这个头文件位置不能和winsock2.h互换
#include <stdio.h>
//#include <windows.h>
#pragma comment(lib, "IPHLPAPI.lib")
#pragma comment(lib,"ws2_32.lib")
int main()
{
WORD wVersionRequested = MAKEWORD(2, 2);//版本
WSADATA wsaDATA;
//打开网络库
WSAStartup(wVersionRequested, &wsaDATA);
PFIXED_INFO pInfo = (FIXED_INFO*)HeapAlloc(GetProcessHeap(), 0, sizeof(FIXED_INFO));//传址引用,由函数填充该结构体
if (NULL == pInfo)
{
printf("PFIXED_INFO结构体申请内存失败!\n");
return -1;
}
ULONG InfoLength = 0;
if (GetNetworkParams(pInfo, &InfoLength) == ERROR_BUFFER_OVERFLOW) //内存溢出
{
HeapFree(GetProcessHeap(), 0, pInfo);//释放
pInfo = (FIXED_INFO*)HeapAlloc(GetProcessHeap(), 0, InfoLength);//以实际大小再申请一次
printf("PFIXED_INFO结构体申请内存不够大,再来一次!\n");
if (NULL == pInfo) {
printf("PFIXED_INFO结构体申请内存失败!\n");
return 1;
}
}
if (int re = GetNetworkParams(pInfo, &InfoLength) == NO_ERROR) {
printf("主机名为: %s\n", pInfo->HostName);
printf("域名为: %s\n", pInfo->DomainName);
printf("DNS服务器为:\n");
printf("\t%s\n", pInfo->DnsServerList.IpAddress.String);
IP_ADDR_STRING* pIPAddr;
pIPAddr = pInfo->DnsServerList.Next;//这里是链表的形式
while (pIPAddr) {
printf("\t%s\n", pIPAddr->IpAddress.String);
pIPAddr = pIPAddr->Next;
}
printf("节点类型为: ");
switch (pInfo->NodeType) {
case BROADCAST_NODETYPE:
printf("广播\n");
break;
case PEER_TO_PEER_NODETYPE:
printf("点到点\n");
break;
case MIXED_NODETYPE:
printf("广播优先\n");
break;
case HYBRID_NODETYPE:
printf("点到点优先\n");
break;
default:
printf("未知类型 %0lx\n", pInfo->NodeType);
break;
}
printf("DHCP作用域: %s\n", pInfo->ScopeId);//应该是子网掩码表示法,例如:255.255.255.0
if (pInfo->EnableRouting)
printf("IP路由已启用\n");
else
printf("IP路由未启用\n");
if (pInfo->EnableProxy)
printf("ARP代理已启用\n");
else
printf("ARP代理未启用\n");
if (pInfo->EnableDns)
printf("DNS服务已启用\n");
else
printf("DNS服务未启用\n");
}
else
{
printf("获取本地网络参数失败,错误码为: %d\n", re);
return 1;
}
if (pInfo)
{
HeapFree(GetProcessHeap(), 0, pInfo);
}
WSACleanup();
return 0;
}
运行结果:

ipconfig /all的结果:

参数②
参数①的所需长度,传址调用。
这里需要两次调用GetNetworkParams,因为第一次调用函数时,申请用来存放该函数返回的FIXED_INFO结构体内存可能不够大,因此把之前申请的内存释放,重新按照FIXED_INFO实际大小来申请内存,然后再次调用。
因为FIXED_INFO结构体中的成员有些是链表结构,这些链表长度是不定的,例如DNS服务器IP,有设置1个,也有设置2个的,所以申请的结构体大小不一定够大。就好比同样一道菜,在南方可能用小盘子装就够,到了北方就需要换大碗来装了。这个思想在下面也将用到。
扩展:
malloc和HeapAlloc都是从堆分配一块内存,不同的是前者属于C++标准函数库,通过free()释放,后者是Windows API 函数,通过HeapFree释放内存。HeapAlloc比malloc更加灵活,它允许指定分配堆,适用于为进程分配多个堆的应用场景。这里使用HeapAlloc是因为参考了官方的范例代码。
获取网卡配置信息
GetAdaptersInfo
https://docs.microsoft.com/en-us/windows/win32/api/iphlpapi/nf-iphlpapi-getadaptersinfo
IPHLPAPI_DLL_LINKAGE ULONG GetAdaptersInfo(
[out] PIP_ADAPTER_INFO AdapterInfo,
[in, out] PULONG SizePointer
);
参数①
网卡信息的链表,有几块网卡链表就有几个元素
https://docs.microsoft.com/en-us/windows/win32/api/iptypes/ns-iptypes-ip_adapter_info
typedef struct _IP_ADAPTER_INFO {
struct _IP_ADAPTER_INFO *Next;//链表因此是指向下一个网卡信息的指针
DWORD ComboIndex;//保留参数
char AdapterName[MAX_ADAPTER_NAME_LENGTH + 4];//网卡标识符,例如:7BB2D787-24CE-429D-824B-E35875D76B2D
char Description[MAX_ADAPTER_DESCRIPTION_LENGTH + 4];//网卡描述,例如:Bluetooth Device (Personal Area Network)
UINT AddressLength;//MAC地址长度,应该是6字节,48位
BYTE Address[MAX_ADAPTER_ADDRESS_LENGTH];//MAC地址,表示为12个16进制数
DWORD Index;//和上面ComboIndex返回的一样的值,网卡编号
UINT Type;//网卡类型,一般是以太网
UINT DhcpEnabled;//是否启用了DHCP服务
PIP_ADDR_STRING CurrentIpAddress;//保留参数
IP_ADDR_STRING IpAddressList;//本地IP地址列表
IP_ADDR_STRING GatewayList;//虽然是列表,通常网关只有一个
IP_ADDR_STRING DhcpServer;//DHCP服务器IP地址
BOOL HaveWins;//是否启用Windows Internet Name Service
IP_ADDR_STRING PrimaryWinsServer;//上个参数为TRUE才生效,通常主服务器只有一个IP
IP_ADDR_STRING SecondaryWinsServer;//上上个参数为TRUE才生效,通常副服务器可以有多个IP
time_t LeaseObtained;//DHCP租期获取的时间
time_t LeaseExpires;//DHCP租期到期时间
} IP_ADAPTER_INFO, *PIP_ADAPTER_INFO;
成员6网卡类型有如下几种:
MIB_IF_TYPE_OTHER:Some other type of network interface.
MIB_IF_TYPE_ETHERNET:An Ethernet network interface.以太网
IF_TYPE_ISO88025_TOKENRING:MIB_IF_TYPE_TOKENRING.令牌环网
MIB_IF_TYPE_PPP:A PPP network interface.Point to Point Protocol点到点网络
MIB_IF_TYPE_LOOPBACK:A software loopback network interface.软件环回网
MIB_IF_TYPE_SLIP:An ATM network interface.Asynchronous Transfer Mode,异步传输网络
IF_TYPE_IEEE80211:An IEEE 802.11 wireless network interface.无线网,在一些系统也返回以太网
参数②
参数①的所需长度,传址调用。
获取IP地址信息
GetAdaptersAddresses相当于GetAdaptersInfo的加强版,后者只支持IPv4,前者支持IPv4+IPv6。
https://docs.microsoft.com/en-us/windows/win32/api/iphlpapi/nf-iphlpapi-getadaptersaddresses
IPHLPAPI_DLL_LINKAGE ULONG GetAdaptersAddresses(
[in] ULONG Family,
[in] ULONG Flags,
[in] PVOID Reserved,
[in, out] PIP_ADAPTER_ADDRESSES AdapterAddresses,
[in, out] PULONG SizePointer
);
参数①ULONG Family
获取的IP地址簇类型,可以设置为AF_UNSPEC(0)同时获取IPv4和IPv6的信息或者设置为AF_INET(IPv4)、AF_INET6(IPv6)。
参数②ULONG Flags
获取的地址类型,这里的类型可以设置多个,用按位【与】区分。若填写0,则下面几种地址都同时获取:
Unicast Addresses:单播地址
Anycast Addresses:任播地址
Multicast Addresses:多播地址
参数③PVOID Reserved
保留参数
参数④PIP_ADAPTER_ADDRESSES AdapterAddresses
网卡对应的IP地址,这里是一个列表和GetAdaptersInfo函数中的地址一样。
参数⑤PULONG SizePointer
参数④的大小,传址调用。
获取网卡索引与名称
GetInterfaceInfo
https://docs.microsoft.com/en-us/windows/win32/api/iphlpapi/nf-iphlpapi-getinterfaceinfo
IPHLPAPI_DLL_LINKAGE DWORD GetInterfaceInfo(
[out] PIP_INTERFACE_INFO pIfTable,
[in, out] PULONG dwOutBufLen
);
//获取网卡编号与名称
int main()
{
WORD wVersionRequested = MAKEWORD(2, 2);//版本
WSADATA wsaDATA;
if (WSAStartup(wVersionRequested, &wsaDATA) != 0)//打开网络库
{
printf("打开网络库失败!\n");
return -1;
}
PIP_INTERFACE_INFO pInfo = NULL;//(PIP_ADAPTER_INFO)HeapAlloc(GetProcessHeap(), 0, sizeof(PIP_ADAPTER_INFO));//传址引用,由函数填充该结构体
ULONG InfoLength = 0;
DWORD re = GetInterfaceInfo(NULL, &InfoLength);//首次调用,先获取需要分配内存的大小
if (re == ERROR_INSUFFICIENT_BUFFER)
{
pInfo = (PIP_INTERFACE_INFO)HeapAlloc(GetProcessHeap(), 0, InfoLength);
if (pInfo == NULL)
{
printf("PIP_INTERFACE_INFO结构体申请内存失败!\n");
return -1;
}
}
re = GetInterfaceInfo(pInfo, &InfoLength);
if (re == NO_ERROR)
{
printf("Number of Adapters: %ld\n\n", pInfo->NumAdapters);
for (int i = 0; i < pInfo->NumAdapters; i++) {
printf("网卡索引[%d]: %ld\n", i,
pInfo->Adapter[i].Index);
printf("网卡名称[%d]: %ws\n\n", i,
pInfo->Adapter[i].Name);
}
}
else if (re == ERROR_NO_DATA)
{
printf("本地系统不存在安装有IPv4的网卡\n");
}
else
{
printf("获取网卡编号与名称出错,错误码是: %d\n", re);
return -1;
}
if (pInfo)
{
HeapFree(GetProcessHeap(), 0, pInfo);
}
return 0;
}
运行结果:

网卡名字那么要和GetAdaptersInfo里面那个description要区分开。
参数①PIP_INTERFACE_INFO pIfTable
PIP_INTERFACE_INFO结构体
https://docs.microsoft.com/en-us/windows/win32/api/ipexport/ns-ipexport-ip_interface_info
typedef struct _IP_INTERFACE_INFO {
LONG NumAdapters;
IP_ADAPTER_INDEX_MAP Adapter[1];
} IP_INTERFACE_INFO, *PIP_INTERFACE_INFO;
成员①:网卡数量
成员②IP_ADAPTER_INDEX_MAP数组
IP_ADAPTER_INDEX_MAP数组
https://docs.microsoft.com/en-us/windows/win32/api/ipexport/ns-ipexport-ip_adapter_index_map
typedef struct _IP_ADAPTER_INDEX_MAP {
ULONG Index;
WCHAR Name[MAX_ADAPTER_NAME];
} IP_ADAPTER_INDEX_MAP, *PIP_ADAPTER_INDEX_MAP;
成员①:网卡编号
成员②:网卡名称
参数②PULONG dwOutBufLen
参数①的大小,传址调用。
释放DHCP的IP地址
IpReleaseAddress
https://docs.microsoft.com/en-us/windows/win32/api/iphlpapi/nf-iphlpapi-ipreleaseaddress
IPHLPAPI_DLL_LINKAGE DWORD IpReleaseAddress(
[in] PIP_ADAPTER_INDEX_MAP AdapterInfo
);
PIP_ADAPTER_INDEX_MAP这个参数在GetInterfaceInfo中可以获取
参数PIP_ADAPTER_INDEX_MAP AdapterInfo
https://docs.microsoft.com/en-us/windows/win32/api/ipexport/ns-ipexport-ip_adapter_index_map
typedef struct _IP_ADAPTER_INDEX_MAP {
ULONG Index;
WCHAR Name[MAX_ADAPTER_NAME];
} IP_ADAPTER_INDEX_MAP, *PIP_ADAPTER_INDEX_MAP;
成员①
网卡的索引
网卡的索引在网卡禁用和启用后会变化。
成员②
网卡的名称
重新获取DHCP的IP地址
IpRenewAddress
https://docs.microsoft.com/en-us/windows/win32/api/iphlpapi/nf-iphlpapi-iprenewaddress
IPHLPAPI_DLL_LINKAGE DWORD IpRenewAddress(
[in] PIP_ADAPTER_INDEX_MAP AdapterInfo
);
相当于对指定网卡执行
ipconfig /renew
参数与上面的函数一样,不再赘述。
该命令获得的IP地址不一定和原IP一样。
获取网络地址列表
GetIpAddrTable
https://docs.microsoft.com/en-us/windows/win32/api/iphlpapi/nf-iphlpapi-getipaddrtable
IPHLPAPI_DLL_LINKAGE DWORD GetIpAddrTable(
[out] PMIB_IPADDRTABLE pIpAddrTable,
[in, out] PULONG pdwSize,
[in] BOOL bOrder
);
参数①PMIB_IPADDRTABLE pIpAddrTable
IPv4地址结构MIB_IPADDRTABLE 列表
typedef struct _MIB_IPADDRTABLE {
DWORD dwNumEntries;
MIB_IPADDRROW table[ANY_SIZE];
} MIB_IPADDRTABLE, *PMIB_IPADDRTABLE;
成员①:IP地址个数
成员②:MIB_IPADDRROW结构
typedef struct _MIB_IPADDRROW_W2K {
DWORD dwAddr;//字节序的IP地址
DWORD dwIndex;//该IP地址对应的网卡编号
DWORD dwMask;//字节序的子网掩码
DWORD dwBCastAddr;//字节序的广播地址,GetIpAddrTable函数不返回此成员的正确值。
DWORD dwReasmSize;//接收数据报的最大重新组装大小
unsigned short unused1;//保留参数
unsigned short unused2;//保留参数
} MIB_IPADDRROW_W2K, *PMIB_IPADDRROW_W2K;
参数②PULONG pdwSize
参数①的大小
参数③BOOL bOrder
是否按地址排序,设置为TRUE,则按升序排列:
192.168.0.254
192.168.0.255
192.168.1.0
192.168.1.1
int main()
{
DWORD re = 0;
DWORD dwSize = 0;
DWORD ifIndex;
IN_ADDR IPAddr;
PMIB_IPADDRTABLE pIPAddrTable;
GetIpAddrTable(NULL, &dwSize, 0);
pIPAddrTable = (PMIB_IPADDRTABLE)HeapAlloc(GetProcessHeap(), 0, dwSize);
if ((re = GetIpAddrTable(pIPAddrTable, &dwSize, 0)) != NO_ERROR)
{
printf("获取地址列表出错,错误码为: %d\n", re);
HeapFree(GetProcessHeap(), 0, pIPAddrTable);
return -1;
}
printf("\tNum Entries: %ld\n", pIPAddrTable->dwNumEntries);
for (int i = 0; i < (int)pIPAddrTable->dwNumEntries; i++) {
printf("\n\tInterface Index[%d]:\t%ld\n", i, pIPAddrTable->table[i].dwIndex);
IPAddr.S_un.S_addr = (u_long)pIPAddrTable->table[i].dwAddr;
printf("\tIP Address[%d]: \t%s\n", i, inet_ntoa(IPAddr));
IPAddr.S_un.S_addr = (u_long)pIPAddrTable->table[i].dwMask;
printf("\tSubnet Mask[%d]: \t%s\n", i, inet_ntoa(IPAddr));
IPAddr.S_un.S_addr = (u_long)pIPAddrTable->table[i].dwBCastAddr;
printf("\tBroadCast[%d]: \t%s (%ld%)\n", i, inet_ntoa(IPAddr), pIPAddrTable->table[i].dwBCastAddr);
printf("\tReassembly size[%d]:\t%ld\n", i, pIPAddrTable->table[i].dwReasmSize);
printf("\tType and State[%d]:", i);
if (pIPAddrTable->table[i].wType & MIB_IPADDR_PRIMARY)
printf("\tPrimary IP Address");
if (pIPAddrTable->table[i].wType & MIB_IPADDR_DYNAMIC)
printf("\tDynamic IP Address");
if (pIPAddrTable->table[i].wType & MIB_IPADDR_DISCONNECTED)
printf("\tAddress is on disconnected interface");
if (pIPAddrTable->table[i].wType & MIB_IPADDR_DELETED)
printf("\tAddress is being deleted");
if (pIPAddrTable->table[i].wType & MIB_IPADDR_TRANSIENT)
printf("\tTransient address");
printf("\n");
}
if (pIPAddrTable) {
HeapFree(GetProcessHeap(), 0, pIPAddrTable);
pIPAddrTable = NULL;
}
return 0;
}
处理IN_ADDR报错的解决方法是加一行
#define _WINSOCK_DEPRECATED_NO_WARNINGS
运行结果如下,可以看到这里的IP地址列表只有5个,小于上面网卡数量,因为有些网卡没有设置IP地址。
广播地址都是错误的,均为1.0.0.0。

可以看到
为网卡增加一个IP地址
AddIPAddress
https://docs.microsoft.com/en-us/windows/win32/api/iphlpapi/nf-iphlpapi-addipaddress
IPHLPAPI_DLL_LINKAGE DWORD AddIPAddress(
[in] IPAddr Address,
[in] IPMask IpMask,
[in] DWORD IfIndex,
[out] PULONG NTEContext,
[out] PULONG NTEInstance
);
参数①IPAddr Address
要添加的IPv4地址,是IPAddr类型结构体
https://docs.microsoft.com/en-us/windows/win32/api/inaddr/ns-inaddr-in_addr
之前有讲过,不重复了。
参数②IPMask IpMask
子网掩码,格式与参数①一样。
参数③DWORD IfIndex
网卡索引,使用GetIpAddrTable或者GetAdaptersInfo来获取
参数④PULONG NTEContext
NTEContext是删除这个IP地址设置的标识
参数⑤PULONG NTEInstance
NTE Instance指针,暂时未发现有其他作用
返回值
成功返回NO_ERROR
失败返回相应错误码
注意:如果执行成功,那么对应网卡不能使用DHCP分配的IP,上面有关DHCP的IpReleaseAddress函数失效。
直接添加会获得ERROR_ACCESS_DENIED(5)的错误码。

注意:
1.AddIPAddress增加的IP是临时的,当系统重新启动或将网卡禁用,然后启用,IP地址就会消失。
2.AddIPAddress增加的IP只能用ipconfig查看,在网络连接中查看IPv4属性是看不到的。
3.如果出现错误码为:
ERROR_ACCESS_DENIED(5)
需要在debug目录下找到编译好的exe文件,右键选择【以管理员身份运行】
4.如果出现错误码为:
ERROR_OBJECT_ALREADY_EXISTS(5010)
说明该IP地址已经设置成功,不能重复设置。
//添加IP地址
UINT iaIPAddress;
UINT iaIPMask;
iaIPAddress = inet_addr("192.168.22.25");
iaIPMask = inet_addr("255.255.255.0");
ifIndex = pIPAddrTable->table[1].dwIndex;//要根据GetIpAddrTable取结果
ULONG NTEContext = 0;
ULONG NTEInstance = 0;
re = AddIPAddress(iaIPAddress, iaIPMask, ifIndex, &NTEContext, &NTEInstance);
if (re != NO_ERROR) {
printf("添加IP地址失败,错误码为: %d\n", re);
}

为网卡删除一个IP地址
DeleteIPAddress
https://docs.microsoft.com/en-us/windows/win32/api/iphlpapi/nf-iphlpapi-deleteipaddress
IPHLPAPI_DLL_LINKAGE DWORD DeleteIPAddress(
[in] ULONG NTEContext
);
只能删除AddIPAddress添加的IP
实验思路:添加一个IP,然后设置断点或getchar暂停,用ipconfig查看结果,然后继续执行DeleteIPAddress。
一个网卡可以设置多个IP地址,每个IP地址可以形成单独的接口(interface),也就是一个网卡对应多个接口。

被折叠的 条评论
为什么被折叠?



