windows获得本机IPV6地址(网络编程)

本程序可以获得网卡的IPV6地址(如果有)

排除tunnel adapter地址(隧道适配器)、环回地址和无法使用的IP

代码如下:

#include <WS2tcpip.h> 
#include <Iphlpapi.h> 
#include <assert.h>
#include <stdio.h>
#pragma comment(lib, "IPHLPAPI.lib")
#pragma   comment(lib, "ws2_32.lib") 


bool InitWSA();
int getLocalIPV6(char *IPAddr);

int main()
{
	bool bRetVal = InitWSA();
	assert(bRetVal);
	char ipv6[512];
	ZeroMemory(ipv6,512);
	int iRetVal = getLocalIPV6(ipv6);
	if(iRetVal != 0){
		printf("error occur!\n");
		exit(-1);
	}
	printf("%s",ipv6);
	WSACleanup();
}

bool InitWSA()
{
	WSADATA wsaData;
	int err;
	err = WSAStartup(MAKEWORD(2,2),&wsaData);
	if(err != 0){
		printf("WSAStartup failed with error: %d\n",err);
		return false;
	}

	/* Confirm that the WinSock DLL supports 2.2.*/
	/* Note that if the DLL supports versions greater    */
	/* than 2.2 in addition to 2.2, it will still return */
	/* 2.2 in wVersion since that is the version we      */
	/* requested.										*/
	if(LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2){
		/* Tell the user that we could not find a usable */
		/* WinSock DLL.                                  */
		printf("Could not find a usable version of Winsock.dll\n");
		WSACleanup();
		return false;
	}
	return true;
}

int getLocalIPV6(char *IPAddr)
{
	PIP_ADAPTER_ADDRESSES pAddresses = NULL;
	PIP_ADAPTER_UNICAST_ADDRESS pCurrentUnicastAddr = NULL;
	DWORD dwRetVal = 0;
	
	const DWORD dwSize = 512;
	DWORD bufflen=dwSize;
	char buff[dwSize];

	int addrLen = sizeof(SOCKADDR_STORAGE);
	int Flags = GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST 
		| GAA_FLAG_SKIP_DNS_SERVER; //这里只需要unicast address(或者按各自的要求)
	ULONG outBufLen = sizeof(IP_ADAPTER_ADDRESSES);
	ULONG family = AF_INET6;
	ULONG Iteration = 3;
	do{
		pAddresses = (IP_ADAPTER_ADDRESSES*) malloc(outBufLen);
		if(pAddresses == NULL){
			printf("Memory allocation failed for IP_ADAPTER_ADDRESS struct\n");
			exit(1);
		}

		dwRetVal = GetAdaptersAddresses(family,0,NULL,pAddresses,&outBufLen);

		if(dwRetVal == ERROR_BUFFER_OVERFLOW){
			free(pAddresses);
			pAddresses = NULL;
		}else{
			break;
		}
		Iteration --;
	}while((dwRetVal == ERROR_BUFFER_OVERFLOW) && (Iteration >= 0));
	


	// 第二次调用GetAdaptersAddresses 获取实际想获取的信息.
	if (dwRetVal == NO_ERROR) {
		PIP_ADAPTER_ADDRESSES pCurrAddresses = pAddresses;
		while (pCurrAddresses) 
		{
			//去除cluster类型的IP,具体的请在MSDN上找这个结构体的说明
			if(pCurrAddresses->IfType != IF_TYPE_ETHERNET_CSMACD){
				pCurrAddresses = pCurrAddresses->Next;
				continue;
			}
			pCurrentUnicastAddr = pCurrAddresses->FirstUnicastAddress;
			while(pCurrentUnicastAddr != NULL){
				if( (pCurrentUnicastAddr->Flags != IP_ADAPTER_ADDRESS_DNS_ELIGIBLE) 
					|| (pCurrentUnicastAddr->DadState == IpDadStateInvalid)){
					pCurrentUnicastAddr = pCurrentUnicastAddr->Next;
					continue;
				}
				INT brtVal = WSAAddressToStringA(pCurrentUnicastAddr->Address.lpSockaddr,
					pCurrentUnicastAddr->Address.iSockaddrLength,NULL,buff,&bufflen);
				if(brtVal != 0){
					int errorcode = WSAGetLastError();
					switch (errorcode){
						case WSAEFAULT:	return 1;
						case WSAEINVAL :return 2;
						case WSANOTINITIALISED:return 3;
						case WSAENOBUFS:	return 4;
						default: return 5;
					}
				}
				printf("%s\n",buff);
				//我为了显示所有符合条件的地址。
				//若准备实际使用的话,把上面一行注释掉,下面的几行去掉注释
				//if (strncmp(buff,"fe80",4)==0||strcmp(buff,"::1") ==0)
				//{
				//}
				//else
				//{
				//	strcpy(IPAddr,buff);
				//	return 0;
				//}
				pCurrentUnicastAddr = pCurrentUnicastAddr->Next;
				ZeroMemory(buff,dwSize);
			}
			pCurrAddresses = pCurrAddresses->Next;
		}
	}
	return 0;
}

代码在我的机器(win7 , vs2008上运行正常)(本机上有3个网卡:一个teamview软件的网卡,一个真实网卡,还有个virtualbox host only 的网卡

最后得到了一个正确的IPV6地址


代码中使用了

ULONG WINAPI GetAdaptersAddresses(
  __in     ULONG Family,
  __in     ULONG Flags,
  __in     PVOID Reserved,
  __inout  PIP_ADAPTER_ADDRESSES AdapterAddresses,
  __inout  PULONG SizePointer
);
MSDN: http://msdn.microsoft.com/en-us/library/aa365915(VS.85).aspx

获得网卡信息,因为只需要IPV6单播地址,故Flags = GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST 
| GAA_FLAG_SKIP_DNS_SERVER;

然后检查IP_ADAPTER_ADDRESSES 结构体:http://msdn.microsoft.com/en-us/library/aa366058(v=VS.85).aspx

IP_ADAPTER_ADDRESSES 结构体中只要真实的网卡IP,故检查IP_ADAPTER_ADDRESSES::IfType = IF_TYPE_ETHERNET_CSMACD (An Ethernet network interface.)

这里的话还可以改成排除IF_TYPE_TUNNEL类型(隧道适配器)  (这里根据需要设定)

然后在得到IP_ADAPTER_ADDRESSES::FirstUnicastAddress 

类型为PIP_ADAPTER_UNICAST_ADDRESS  , MSDN:http://msdn.microsoft.com/en-us/library/windows/desktop/aa366066(v=vs.85).aspx

再检查此结构体中的Flags = IP_ADAPTER_ADDRESS_DNS_ELIGIBLE (指这个IP地址是出现在DNS中是合法的)
和DadState 为以下值:  我只排除IpDadStateInvalid标志的IP地址
IpDadStateInvalid

The DAD state is invalid.

IpDadStateTentative

The DAD state is tentative.

IpDadStateDuplicate

A duplicate IP address has been detected.

IpDadStateDeprecated

The IP address has been deprecated.

IpDadStatePreferred

The IP address is the preferred address.


最终得到的IPV6地址是用SOCKADDR结构体表示的
最后用函数
INT WSAAPI WSAAddressToString(
  __in      LPSOCKADDR lpsaAddress,
  __in      DWORD dwAddressLength,
  __in_opt  LPWSAPROTOCOL_INFO lpProtocolInfo,
  __inout   LPTSTR lpszAddressString,
  __inout   LPDWORD lpdwAddressStringLength
);
将SOCKADDR转换成IPV6字符串格式

至于SOCKADDR怎么转换成IN6_ADDR,我也不知道了,刚学。
可以用曲线转换方法,用这个方法得到char*字符串,然后再用字符串转换成IN6_ADDR类型(有API调用)


OK, 结束

转载请注明出处

你可能感兴趣的:(编程,windows,网络,null,Allocation,winapi)