获取网络配置信息


记录一下网络编程中获取当前本机网络配置的一些函数。

获取网络配置参数

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),也就是一个网卡对应多个接口。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

oldmao_2000

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值