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

目前ipv6的应用越来越广泛,慢慢将变成强制的标准,做网络应用开发的,在未来会越来越的要开始支持ipv6,但是ipv6肯定不是一下子就可以大范围应用的,必然有一段时间Ipv4与ipv6共存,要做到对ipv4和ipv6的同时支持。

 

关于如何让程序支持ipv6 可以参考  https://blog.csdn.net/v6543210/article/details/106927210

示例代码见: https://github.com/alongL/ipv6_demo

这个文章只讲如何兼容ipv4和ipv6

如果使用asio这样成熟的网络库,词汇语法用再看此文,asio已经将底层的差别屏蔽。

这里只讨论从socket层面支持ipv4 和ipv6会遇到的情况。

 

1.地址表示

使用这样的结构体既可以表示ipv4的地址,又可以表示ipv6地址

struct myaddrinfo{
    int     ai_family;     
	union 
	{
		struct sockaddr addr;
		struct sockaddr_in addr4;
		struct sockaddr_in6 addr6;
	};
};

union联合体中的三个结构体 大概如下所示(与标准有一些区别,主要方便理解): 源自https://blog.csdn.net/albertsh/article/details/80991684

/* Structure describing a generic socket address.  */
struct sockaddr
{
 uint16 sa_family;           /* Common data: address family and length.  */
 char sa_data[14];           /* Address data.  */
};

/* Structure describing an Internet socket address.  */
struct sockaddr_in
{
 uint16 sin_family;          /* Address family AF_INET */ 
 uint16 sin_port;            /* Port number.  */
 uint32 sin_addr.s_addr;     /* Internet address.  */
 unsigned char sin_zero[8];  /* Pad to size of `struct sockaddr'.  */
};

/* Ditto, for IPv6.  */
struct sockaddr_in6
{
 uint16 sin6_family;         /* Address family AF_INET6 */
 uint16 sin6_port;           /* Transport layer port # */
 uint32 sin6_flowinfo;       /* IPv6 flow information */
 uint8  sin6_addr[16];       /* IPv6 address */
 uint32 sin6_scope_id;       /* IPv6 scope-id */
};

 

2.地址使用

此处由于windows平台下和linux平台下对sockaddr_in6的定义有些区别,所以需要尽量使用两个平台下定义相同的变量,避免使用系统中定义名称的变量。

 

获取ip地址

	//返回ipv4 地址
	static uint32_t get_ipv4(const myaddrinfo &addr)
	{
		return addr.addr4.sin_addr.s_addr;
	}

	//返回ipv6 地址
	static in6_addr get_ipv6(const myaddrinfo &addr)
	{
		return addr.addr6.sin6_addr;
	}	

ip地址字符串

    //返回ipv4或v6地址字符串
	static inline std::string get_ipstr(const myaddrinfo &addr) 
	{
		if (addr.ai_family == AF_INET)
		{
			char buf[INET_ADDRSTRLEN] = "";
			inet_ntop(AF_INET, (void*) &(addr.addr4.sin_addr), buf, INET_ADDRSTRLEN);
			return std::string(buf);
		}
		else if (addr.ai_family == AF_INET6)
		{
			char buf[INET6_ADDRSTRLEN] = "";
			inet_ntop(AF_INET6, (void*) &(addr.addr6.sin6_addr), buf, INET6_ADDRSTRLEN);
			return std::string(buf);
		}
		else return std::string("");
	}

获取端口号

	static inline uint16_t get_port(const myaddrinfo &addr)
	{
		if (addr.ai_family == AF_INET)
		{
			return ntohs(addr.addr4.sin_port);
		}
		else if (addr.ai_family == AF_INET6)
		{
			return ntohs(addr.addr6.sin6_port);
		}
		else return 0;
	}

4. ipv4到ipv6的地址映射

如果程序监听的是  ::0  地址,即IPv6地址, 对于同时具有ipv4地址和 ipv6地址的 双栈服务器来讲, 会同时监听ipv4和ipv6协议栈上的某个端口,客户端连接服务器的时候,如果使用的是ipv4连接的,显示出的结果是 ::ff:192.168.3.8这样经过映射的ipv6的地址。

如果要判断是否是ipv4映射的地址,可以用如下办法

	//是否是v4映射的v6地址
	static bool is_v4mapped(const myaddrinfo &addr)
	{
		struct Prefix {
			int64_t a = 0x0;
			int16_t b = 0x0000;
			int16_t c = 0xFFFF;
		};
		static const Prefix prefix;
		static const int PREFIX_SIZE = 12;
		// [00:00:00:00:00 :00:00:00:00:00 :ff:ff] 共12个字节 
		if (memcmp(&prefix, &(addr.addr6.sin6_addr), PREFIX_SIZE) == 0)
			return true;
		else
			return false;
	}

	//ipv4地址,映射前的地址 (网络字节序)
	static uint32_t get_ipv4_inv6(const myaddrinfo &addr)
	{
		if (addr.ai_family == AF_INET)
			return  addr.addr4.sin_addr.s_addr;
		
		int32_t v4 = *(int32_t*)(addr.addr6.sin6_addr.s6_addr + 12);
		return v4;
	}

 

更详细的例子可以参考:

https://github.com/alongL/ipv6_demo

 

 

 

参考:

https://stackoverflow.com/questions/26531531/efficient-way-to-store-ipv4-ipv6-addresses/63842120#63842120

nginx 的实现 https://github.com/nginx/nginx/blob/912fb44e25c6ab2598e36b4544c709b871251b2e/src/core/ngx_inet.h#L44

ipv6编程指引 http://www.6ip.cu/pdf/long_trans_app_ipv6.pdf

https://stackoverflow.com/questions/14272656/how-to-resolve-ipv4-address-from-ipv4-mapped-ipv6-address

©️2020 CSDN 皮肤主题: 1024 设计师:上身试试 返回首页