IPv6与IPv4的互操作性

2019/06/24 C Network

IPv6与IPv4的互操作性

缘起

最近看见很多app的启动页面上都支持IPv6标志。说起来IPv6已经出现很多年了,但是实际应用中却一直没有能被广泛使用。
而实际上早在2000年以前操作系统就已经能支持双协议栈(dual stacks),可以实现IPv4和v6的互通了。

实现原理

IPv4 和 IPv6 互联时,操作系统内核根据具体情况,在数据包通过IP协议层时将IPv6地址和IPv4地址互相转化,应用层不需要知道转化的细节

IPv4地址可以映射层IPv6地址,用户代码唯一要做的是调用getaddrinfo进行dns解析时,hints参数设置AI_V4MAPPED标志

下面时具体步骤

IPv4客户端连接IPv6服务端

  1. IPv6的服务器启动IPv6套接字
    //note/c/network/server_compatible_ipv6.c
     listenfd = socket(AF_INET6, SOCK_STREAM, 0);
    
     bzero(&serv_addr, sizeof(serv_addr));
     serv_addr.sin6_family = AF_INET6;
     // ipv6的服务器默认地址是 /* :: */
     // 回环地址时   /* ::1 */
     serv_addr.sin6_addr = in6addr_any;  /* :: */
     serv_addr.sin6_port = htons(port);
     bind(listenfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
     listen(listenfd, 10);
    
  2. IPv4调用gethostbyname找到服务器主机的A记录(服务器主机还有一个AAAA记录,对应ipv6地址)
    //note/c/network/client_compatible_ipv6.c
     struct addrinfo hists, *res;
    
     bzero(&hists, sizeof(hists));
     hists.ai_flags = AI_CANONNAME;
     hists.ai_family = AF_UNSPEC;
    
     // 此处没有使用使用gethostbyname,因为这个函数不支持ipv6,但ipv4的客户端一般都是使用gethostbyname
     // getaddrinfo 的 man 文件中显示这个函数支持的版本,需要定义编译常量-D_POSIX_C_SOURCE=200112L(或更大)
     // 但我实际编译的时候不定义也没问题,只是vscode会提示有错误
     getaddrinfo(hostname, server, &hists, &res);
     do{ 
         if(!res)
         {
             return;
         }
         sockfd = socket(res->ai_family, SOCK_STREAM, 0);
         if(res->ai_family == AF_INET)
         {
             if(connect(sockfd, res->ai_addr, res->ai_addrlen) == 0)
             {
                 break;
             }
         }
         // if(res->ai_family == AF_INET6)
         // {
         //     if(connect(sockfd, res->ai_addr, res->ai_addrlen) == 0)
         //     {
         //         break;
         //     }
         // }
         close(sockfd);
    
     }while((res = res->ai_next) != NULL);  
    
  3. 客户调用connect发送IPv4的SYN分节
  4. 服务主机收到这个IPv4 SYN分节,内核设置一个标志指示本连接使用IPv4映射的IPv6地址,同时响应客户一个 IPv4的 SYN/ACK 分节,而此时服务主机的用户代码中acceptf返回的时这个IPv4映射的IPv6地址
    //note/c/network/server_compatible_ipv6.c
     connfd = accept(listenfd, (struct sockaddr *)&cli_addr, &len);
     // IN6_IS_ADDR_V4MAPPED 宏可以测试ipv6地址是否时ipv4映射的
     // 下面是这个宏的代码
     // # define IN6_IS_ADDR_V4MAPPED(a) \
     // (__extension__							      \
     // ({ const struct in6_addr *__a = (const struct in6_addr *) (a);	      \
     // __a->__in6_u.__u6_addr32[0] == 0					      \
     // && __a->__in6_u.__u6_addr32[1] == 0				      \
     // && __a->__in6_u.__u6_addr32[2] == htonl (0xffff); }))
     if(IN6_IS_ADDR_V4MAPPED(&cli_addr.sin6_addr))
     {
         printf("v4mapped address\n");
     }
    
  5. 服务主机向这个IPv4映射的IPv6地址发送TCP分节时,IP栈的目的地址又被内核转换为IPv6地址映射的IPv4地址.

IPv6客户端连接IPv4服务端

  1. IPv4服务器启动一个IPv4监听套接字(只有A记录)
  2. IPv6客户启动后调用getaddrinfo获得服务器IPv6地址(hints结构中设置AI_V4MAPPED标志),因为IPv4服务器只有A记录,getaddrinfo返回的是IPv4映射的IPv6地址
    //note/c/network/netaddr.c
     struct addrinfo hists, *res;
     char            buf[255];
        
     bzero(&hists, sizeof(hists));
     // hists.ai_flags = AI_CANONNAME;
     hists.ai_flags = AI_V4MAPPED;
     // hists.ai_family = AF_UNSPEC;
     hists.ai_family = AF_INET6;
     getaddrinfo(argv[1], argv[2], &hists, &res);
    

    观察一下ipv4和ipv6如何转换映射的

    gcc netaddress.c -o netaddr
    ./netaddr qq.com 8888
    call getaddrinfo
    start
    // 可以看到ipv4映射的ipv6地址的样子
    ::ffff:111.161.64.48
    qq.com
    
  3. IPv6客户调用 connect 连接服务主机时,内核检测到这个映射地址后发送一个IPv4 SYN分节到服务器
  4. 服务器响应 SYN/ACK 分节, 连接建立

Search

    Table of Contents