[IPv6] 兼容IPv4和IPv6的通信模块的实现(windows)

文章目录

  • 实现式样
  • 问题点
  • 问题梳理
    • 1.IPv4地址和IPv6地址的结构体的不同
      • 参考文章
      • 正文
      • 对策
    • 2.获取目标设备的IP地址
    • 3. 本地是否支持IPv6通信
  • 代码实现
    • 比较重要的代码实现
      • 获取目标设备的IP地址
      • 本地是否支持IPv6通信的判断
      • 完整代码
  • 问题
  • 参考文章

实现式样

实现一个模块,可以通过hostname获取目标的IP地址,并且保存IPv4和IPv6地址,
用户可以选择IPv4和或Pv6地址进行通信,模块来创建socket,返回给用户进行操作。

问题点

  1. IPv4地址和IPv6地址的结构体是不一样的,程序要同时支持两种地址结构。
  2. 获取目标设备的IP地址,要能获取IPv4地址和IPv6地址
  3. 判断本地是否支持IPv6通信

问题梳理

1.IPv4地址和IPv6地址的结构体的不同

参考文章

结构体sockaddr、sockaddr_in、sockaddr_in6之间的区别和联系
正文部分,参照了以上文章,这篇文章对源码结构的定义的分析很好。

正文

Socket编程中,一般地址管理有3种结构体:
sockaddr、sockaddr_in、sockaddr_in6
sockaddr_in结构用于IPv4,sockaddr_in6结构用于IPv6通信。
他们结构内部成员是不一样的。

他们的结构如下:

/* /usr/include/bits/socket.h */
/* Structure describing a generic socket address.  */
struct sockaddr
{
__SOCKADDR_COMMON (sa_);    /* Common data: address family and length.  */
char sa_data[14];           /* Address data.  */
};

↑三个结构中,__SOCKADDR_COMMON 部分是一样的,只是宏参数不同,其实都是short类型(2字节),表示family;
sockaddr 独有的sa_data数组,14字节。

/* /usr/include/netinet/in.h */
/* Structure describing an Internet socket address.  */
struct sockaddr_in
{
 __SOCKADDR_COMMON (sin_);
 in_port_t sin_port;         /* Port number.  */
 struct in_addr sin_addr;    /* Internet address.  */

 /* Pad to size of `struct sockaddr'.  */
 unsigned char sin_zero[sizeof (struct sockaddr) -
            __SOCKADDR_COMMON_SIZE -
            sizeof (in_port_t) -
            sizeof (struct in_addr)];
};

↑__SOCKADDR_COMMON,2字节;
sin_port,表示port,2字节
sin_addr,表示IP地址,4字节
sin_zero,8字节,填充尾部

/* /usr/include/netinet/in.h */

#ifndef __USE_KERNEL_IPV6_DEFS

/* Ditto, for IPv6.  */
struct sockaddr_in6
{
 __SOCKADDR_COMMON (sin6_);
 in_port_t sin6_port;        /* Transport layer port # */
 uint32_t sin6_flowinfo;     /* IPv6 flow information */
 struct in6_addr sin6_addr;  /* IPv6 address */
 uint32_t sin6_scope_id;     /* IPv6 scope-id */
};

#endif /* !__USE_KERNEL_IPV6_DEFS */

↑ __SOCKADDR_COMMON ,2字节
sin6_port,表示port,2字节
sin6_flowinfo,4字节
sin6_addr,16字节 (in6_addr 是一个联合体,占16字节)
sin6_scope_id,4字节
所以sockaddr和sockaddr_in 大小相同,16字节,sockaddr_in6 28字节。
因为sockaddr和sockaddr_in的大小相同,在IPv4编程的时候,才可以把sockaddr_in的指针转换成sockaddr的指针,例如:

		//ipv4 dup
	    sockaddr_in addr;
        addr.sin_family = AF_INET;
        addr.sin_addr.s_addr = inet_addr("255.255.255.255");
        addr.sin_port = htons(9100);
        int res = sendto(m_local_fd, buff, strlen(buff), 0,
                         (sockaddr *)&addr, sizeof(addr));

IPv6编程的时候,也会把sockaddr_in6的指针转换为sockaddr的指针(例如下面代码),但是这里应该只是为了标准化,
sockaddr_in6的结构和sockaddr在开头的__SOCKADDR_COMMON 部分以外,有很大不同,所以应该是有些额外的处理的,而不是想sockaddr_in指针到sockaddr指针,这么轻易的转换。

		//ipv6 udp
	    sockaddr_in6 addr;
        addr.sin6_family = AF_INET6;
        inet_pton(AF_INET6, buff, &addr.sin6_addr);        
        addr.sin6_port = htons(9100);
        addr.sin6_scope_id = if_nametoindex("ens33");
        res = sendto(m_local_fd, buff, strlen(buff), 0,
                         (sockaddr *)&addr, sizeof(addr));

对策

sockaddr_in和sockaddr_in6的结构不一样,地址存储在结构中的位置也不一样。
所以设计的模块中,必有要兼容sockaddr_in和sockaddr_in6两个结构。

2.获取目标设备的IP地址

用getaddrinfo()来获取。
[参考文章[()

3. 本地是否支持IPv6通信

  1. 通过getaddrinfo()来获取本地IP地址信息,如果获取的地址family中有AF_INET6,那就是支持IPv6了。
  2. 获取windows OSVERSIONINFOW 版本号,5.1(windwos xp)以后支持,windows xp可以选装

代码实现

地址类的设计:
它可以保存IPv4地址和IPv6地址、hostname。
添加了bIsIPv4addr和bIsIPv6addr标识,标记Ipv4地址和Ipv6地址是否有效。

class Target_IPaddr
{
public:
	Target_IPaddr():bIsIPv4addr(false), bIsIPv6addr(false), bISIpv6Selected(false){}
	~Target_IPaddr() {}
public:
	void SetHostname(const char* hostname);
	void SetIpv4Addr(sockaddr_in& a_IPv4_addr);
	void SetIpv6Addr(sockaddr_in6& a_IPv6_addr);
	sockaddr_in GetIpv4Addr();
	sockaddr_in6 GetIpv6Addr();
	std::string GetHostname();
	bool SetUserSelecIpv6(bool b);
	bool IsUserSelecIpv6();
private:
	bool bIsIPv4addr;
	bool bIsIPv6addr;
	bool bISIpv6Selected;
	sockaddr_in m_IPv4_addr;
	sockaddr_in6 m_IPv6_addr;
	std::string m_hostname;

};

管理模块的设计
在它内部包含了Target_IPaddr对象用来进行地址管理。
他的public接口,用来供用户调用,用户需要输入目标设备的hostname。
IPaddr_Controller通过getaddrinfo()来获取目标设备的IPv4和Ipv6地址,控制台输出。
等待用户选择是用IPv4地址还是IPv6地址通信。
用户选择后,会发送一条用户示例消息“hello client”出去。

class IPaddr_Controller
{
protected:
	IPaddr_Controller() {
		WSADATA lpWSAData;
		WSAStartup(MAKEWORD(2, 2), &lpWSAData);
		bIsLocalSupportIPv6 = IsSupportIpv6();
		pIPaddr = new Target_IPaddr();
		conn_socket = INVALID_SOCKET;
		local_ipv6_scope_id = 0;
	}
	~IPaddr_Controller() {
		if (pIPaddr)
			delete pIPaddr;
		WSACleanup();
	}
public:
	static IPaddr_Controller& CreateIPaddrCtrl()
	{
		static IPaddr_Controller IPaddrCtl;
		return IPaddrCtl;
	}
public:
	bool GetIPaddrbyHostname(const char* hostname);
	bool UserChoice();
	bool SendMsg(const char* msg, int length);
private:
	bool IsSupportIpv6();
	bool IsSysSupportIpv6();
	bool IsGetLocalIpv6Addr();
	bool InitSocket();

private:
	bool bIsLocalSupportIPv6;
	Target_IPaddr* pIPaddr;
	char data[256] = { 0x00 };
private:
	int conn_socket;
public:
	ULONG local_ipv6_scope_id;
};

比较重要的代码实现

获取目标设备的IP地址

bool IPaddr_Controller::GetIPaddrbyHostname(const char* hostname)
{
    bool ret = false;

    struct addrinfo hints, * res = NULL, * p = NULL;
    memset(&hints, 0, sizeof(hints));
    int e;
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    
    e = getaddrinfo(hostname, "9100", &hints, &res);
    if (e != 0)
    {
        std::cout << "get host:" << hostname  <<  " addrinfo error:" << WSAGetLastError() << std::endl;
        return false;
    }

    p = res;
    //这里假设只会获取到一个Ipv4地址和一个Ipv6地址
    bool bNotFindIpv6 = true;;
    while (p != NULL)
    {
        if (p->ai_family == AF_INET)
        {
            this->pIPaddr->SetIpv4Addr(*(sockaddr_in*)p->ai_addr);
        }
        else if (p->ai_family == AF_INET6)
        {
            this->pIPaddr->SetIpv6Addr(*(sockaddr_in6*)p->ai_addr);
            bNotFindIpv6 = false;
            
        }
        p = p->ai_next;
    }
    if (bNotFindIpv6)
    {
        const char* buff = "2409:8a1e:6a62:4f80:20c:29ff:fe22:ff89";
        sockaddr_in6 tmp_addr;
        tmp_addr.sin6_family = AF_INET6;
        inet_pton(AF_INET6, buff, &tmp_addr.sin6_addr);
        tmp_addr.sin6_port = htons(9100);
        tmp_addr.sin6_scope_id = this->local_ipv6_scope_id;
        this->pIPaddr->SetIpv6Addr(tmp_addr);
    }
    this->pIPaddr->SetHostname(hostname);
    freeaddrinfo(res);

    if (UserChoice())
        return InitSocket();
    else
        return false;
}

本地是否支持IPv6通信的判断

bool IPaddr_Controller::IsSupportIpv6()
{
	if (!IsSysSupportIpv6())
		return false;
	if (!IsGetLocalIpv6Addr())
		return false;
	return true;
}
bool IPaddr_Controller::IsSysSupportIpv6()
{
	bool bIPv6Env = FALSE;

	OSVERSIONINFO	VersionInformation;
	VersionInformation.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
	GetVersionEx(&VersionInformation);
	//VER_PLATFORM_WIN32_NT:
	//操作系统是 Windows 7、Windows Server 2008、Windows Vista、Windows Server 2003、Windows XP 或 Windows 2000。
	if (VersionInformation.dwPlatformId == VER_PLATFORM_WIN32_NT) {
		//windows xp及以后
		if ((VersionInformation.dwMajorVersion >= 5 && VersionInformation.dwMinorVersion >= 1) ||
			//Windows Vista 以后
			VersionInformation.dwMajorVersion >= 6)
			bIPv6Env = TRUE;
	}
	return bIPv6Env;
}
//获取本地的地址信息,如果包含AFINET6 family就判定支持IPv6
#define USE_LOCAL_HOST_NAME 0 
//1:获取本地hostname 根据host name 查询IP family 
//0:制定一个端口号,和AI_PASSIVE,获取本地任意地址,查询IP family 
bool IPaddr_Controller::IsGetLocalIpv6Addr()
{
    bool ret = false;

#if USE_LOCAL_HOST_NAME 
    get local host name
    //char errmsg[256] = { 0x00 };
    //struct in_addr addr;

    //char local_host_name[256] = { 0x00 };
    //if (gethostname(local_host_name, 256))
    //{
    //    std::cout << "Get local host name failed, err:" << 
    //        strerror_s(errmsg, 256, WSAGetLastError()) << std::endl;
    //    return false;
    //}
    //std::cout << "local_host_name:" << local_host_name << std::endl;
    //std::cout << std::endl;
#endif
    struct addrinfo hints, * res = NULL, * p = NULL;
    memset(&hints, 0, sizeof(hints));
    int e;
#if USE_LOCAL_HOST_NAME 
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    e = getaddrinfo(local_host_name, NULL, &hints, &res);
#else
    hints.ai_flags = AI_PASSIVE;
    e = getaddrinfo(NULL, "8080", &hints, &res);
#endif   
    if (e != 0)
    {
        std::cout << "get local addrinfo error:" << WSAGetLastError() << std::endl;
        return false;
    }

    p = res;
    char bufff[512] = { 0x00 };
    while (p != NULL)
    {
        if (p->ai_family == AF_INET)
        {
            std::cout << "support IPv4" << std::endl;
            memset(bufff, 0x00, sizeof(bufff));
            inet_ntop(AF_INET, &((sockaddr_in*)p->ai_addr)->sin_addr, bufff, sizeof(bufff));
            std::cout << "local support IPv4 addr:" << bufff << std::endl;
        }
        else if (p->ai_family == AF_INET6)
        {
            std::cout << "support IPv6" << std::endl;
            memset(bufff, 0x00, sizeof(bufff));
            inet_ntop(AF_INET6, &((sockaddr_in6*)p->ai_addr)->sin6_addr, bufff, sizeof(bufff));
            std::cout << "local support IPv4 addr:" << bufff << std::endl;
            std::cout << "ipv6 socopeid:" <<  ((sockaddr_in6*)p->ai_addr)->sin6_scope_id << std::endl;
            this->local_ipv6_scope_id = ((sockaddr_in6*)p->ai_addr)->sin6_scope_id;
            ret = true;
            
        }
        p = p->ai_next;
    }
    freeaddrinfo(res);

	return ret;
}

完整代码

添加到附件资源了。

问题

我用这个代码,输入我本地ubuntu虚拟机的hostname,只能获取到IPv4地址,获取不到IPv6地址。
但是输入“www.baidu.com”的域名,是可以获取到IPv4地址和IPv6地址的。
我不知道虚拟机有什么设置,导致无法获取IPv6地址,其实在本机用Ping -6 命令是可以ping通虚拟机的。
[IPv6] 兼容IPv4和IPv6的通信模块的实现(windows)_第1张图片

参考文章

结构体sockaddr、sockaddr_in、sockaddr_in6之间的区别和联系

如何实现在一个 Socket 应用程序中同时支持 IPv4 和 IPv6

c++对ipv4和ipv6地址的兼容处理

让你的Socket应用兼容IPv6

C++ IPv4与IPv6的兼容编码

如何让C++程序代码支持IPv6?

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