使用getaddrinfo将IPv4地址转换为IPv6地址时,会丢失服务端口

时间:2016-05-23 08:30:20

标签: c macos ipv6 getaddrinfo

我在Mac OS X 10.11.2中测试IPv6,我发现了一个奇怪的问题。

我使用getaddrinfo将主机名解析为IPv6地址:

#include <stdio.h>
#include <netdb.h>
#include <errno.h>
#include <string.h>
#include <arpa/inet.h>

int main(int argc, const char * argv[]) {
struct addrinfo * res, * addr;
struct addrinfo hints;
char buffer[128];
struct sockaddr_in6 * sockaddr_v6;

memset(&hints, 0, sizeof(hints));
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_DEFAULT;
if (getaddrinfo("www.google.com", "80", &hints, &res)) {
//if (getaddrinfo("216.58.199.4", "80", &hints, &res)) {
    printf("getaddrinfo failed with errno(-%d)\n", errno);
    return 0;
}

for (addr = res;addr;addr = addr->ai_next)
{
    if (addr->ai_family == AF_INET6)
    {
        sockaddr_v6 = (struct sockaddr_in6 *)addr->ai_addr;
        printf("ipv6 addr is %s %d)\n", inet_ntop(AF_INET6, &sockaddr_v6->sin6_addr, buffer, sizeof(buffer)), ntohs(sockaddr_v6->sin6_port));
    }
}

freeaddrinfo(res);
return 0;
}

输出

"ipv6 addr is 64:ff9b::d83a:c704 80". everything is ok ! 
"www.google.com" is resolved to "64:ff9b::d83a:c704", sin6_port is 80.

但是当我使用"216.58.199.4"代替"www.google.com"时,"216.58.199.4"IPv4的{​​{1}}地址。

"www.google.com"

输出为//if (getaddrinfo("www.google.com", "80", &hints, &res)) { if (getaddrinfo("216.58.199.4", "80", &hints, &res)) { 。将"ipv6 addr is 64:ff9b::d83a:c704 0"转换为"216.58.199.4"是可以的,但"64:ff9b::d83a:c704"的服务端口很奇怪。

有人可以解释一下吗?

1 个答案:

答案 0 :(得分:3)

这是一个影响iOS 9和Mac OS X 10.11的错误。它已在iOS 10和macOS 10.12中修复,但这里有解决方法,可用于支持运行iOS 9和Mac OS X 10.11的设备:

解决方法1:使用服务名称

如果您使用的是已知或注册的端口号,则可以将服务名称而不是端口号作为字符串传递。在此示例中,只需将"80"替换为"http"

if (getaddrinfo("216.58.199.4", "http", &hints, &res)) {

由于错误仅限于数字解析,因此使用服务名称仍然有效。可以在/etc/services中找到已知服务的完整列表。

解决方法2:手动将端口写入sockaddr

如果您不使用/etc/services中的端口号,则可以手动将正确的端口号写入struct sockaddr。如果你这样做,重要的是:

  • 只有在端口为零时才写入,以确保在修复错误后停用此代码。
  • 请记住,sockaddr使用网络字节顺序,因此您需要使用htons()来转换您的端口号。

以下是适用于您的示例的解决方法:

for (addr = res;addr;addr = addr->ai_next)
{
    if (addr->ai_family == AF_INET6)
    {
        sockaddr_v6 = (struct sockaddr_in6 *)addr->ai_addr;
        // START WORKAROUND
        if (sockaddr_v6->sin6_port == 0)
        {
            sockaddr_v6->sin6_port = htons(80);
        }
        // END WORKAROUND
        printf("ipv6 addr is %s %d)\n", inet_ntop(AF_INET6, &sockaddr_v6->sin6_addr, buffer, sizeof(buffer)), ntohs(sockaddr_v6->sin6_port));
    }
}