IPv6 连接错误 WSAEAFNOSUPPORT

IPv6 connect error WSAEAFNOSUPPORT

我使用主机名解析器为 IPv4 / IPv6 编写了一个小型客户端。 对于 IPv4 和解析器,它很好,但是当连接()时 IPv6 没有问题 WSAGetLastError()说 WSAEAFNOSUPPORT。

我将所有结构(AF_INET -> AF_INET6,SOCKADDR_IN -> SOCKADDR_IN6)切换到 IPv6 版本。

#include <winsock2.h>
#include <ws2tcpip.h>

#pragma comment(lib, "ws2_32.lib")

int main()
{
    printf("Simple_Client IPv4 & IPv6\n\n");

    // Initiates Winsock
    WSADATA WSAData;
    WSAStartup(MAKEWORD(2, 0), &WSAData);

    // Get Parameters IP/PORT and request
    std::string str_HOSTNAME = "mirror.neostrada.nl";
    int PORT = 21;

    // RESOLVE IP
    BOOL is_IPv6 = FALSE;
    std::string str_dest_ip = "";

    addrinfo hints = { 0 };
    hints.ai_flags = AI_ALL;
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;

    addrinfo * pResult;
    getaddrinfo(str_HOSTNAME.c_str(), NULL, &hints, &pResult);

    if (pResult == NULL)
    {
        printf("pResult error\n");
        return -1;
    }

    if (pResult->ai_family == AF_INET)
    {
        printf("getaddrinfo = AF_INET (IPv4)\n");
        is_IPv6 = FALSE;
    }

    if (pResult->ai_family == AF_INET6)
    {
        printf("getaddrinfo = AF_INET6 (IPv6)\n");
        is_IPv6 = TRUE;
    }

    char str[128];
    memset(str, 0, sizeof(str));

    if (is_IPv6 == FALSE) // IPv4
    {
        if (inet_ntop(AF_INET, &(*((ULONG*)&(((sockaddr_in*)pResult->ai_addr)->sin_addr))), str, INET_ADDRSTRLEN))
            str_dest_ip = char_to_string(str, strlen(str)); // Copy char in std::string
        else
            printf("inet_ntop error\n");
    }

    if (is_IPv6 == TRUE) // IPv6
    {
        if (inet_ntop(AF_INET6, &(*((ULONG*)&(((sockaddr_in6 *)pResult->ai_addr)->sin6_addr))), str, INET6_ADDRSTRLEN))
            str_dest_ip = char_to_string(str, strlen(str)); // Copy char in std::string
    }

    printf("%s : %s | Port : %i\n", is_IPv6 ? "IPv6" : "IPv4", str_dest_ip.c_str(), PORT);

    // Connect to the HOSTNAME
    SOCKET sock;

    if (is_IPv6 == TRUE)
    {
        SOCKADDR_IN6 sin;
        sin.sin6_family = AF_INET6;

        if(inet_pton(sin.sin6_family, str_dest_ip.c_str(), &sin) != 1)
            printf("ERROR inet_pton %i\n", WSAGetLastError());

        sin.sin6_port = htons(PORT);
        sock = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);
        if (sock == INVALID_SOCKET)
            return -2;

        if (connect(sock, (struct sockaddr *)&sin, sizeof(sin)) != SOCKET_ERROR)
        {
            printf("Connect Success to %s | PORT : %i\n", str_dest_ip.c_str(), PORT);
        }
        else
        {
            printf("ERROR connect to %s | PORT : %i : %i\n", str_dest_ip.c_str(), PORT, WSAGetLastError());
            Sleep(10000);
            return -2;
        }
    }

    char buf[1024] = { 0 };

    int size_recv = recv(sock, buf, sizeof(buf), 0);

    printf("SIZE RECV = %i | DATA RECV = %s\n", size_recv, char_to_string(buf, size_recv).c_str());

    WSACleanup();
    getchar();
    return 0;
}

如果有人有想法,感谢阅读。

问题出在这里:

inet_pton(sin.sin6_family, str_dest_ip.c_str(), &sin)

这会将 IPv6 地址写入 sin6_family 字段,破坏了整个结构。

应该是:

inet_pton(sin.sin6_family, str_dest_ip.c_str(), &sin.sin6_addr)

在开头 zero-initialize 整个 sin 结构也是一个好主意,因为它的字段比您填写的要多。

您没有正确使用 getaddrinfo()

一方面,getaddrinfo() returns 您忽略的错误代码。

另一方面,getaddrinfo() returns 一个 链表 可能 包含多个混合地址IPv4 and/or IPv6,由于您使用 AF_UNSPEC。如果您只对 IPv6 感兴趣,请将 hints.ai_family 设置为 AF_INET6 而不是 AF_UNSPEC.

但无论哪种方式,一个给定的主机名可能有多个与之关联的 IP,并且它们可能无法从您的位置访问,因此您应该 connect()'ing 到列表中的每个地址,或者一次一个或并行,直到其中一个成功。

此外,在这种情况下根本不需要使用 inet_pton()(正如@rustyx 的回答所解释的,您没有正确使用它)。 getaddrinfo() returns 完全填充 sockaddr_in(6) 结构,您可以将 as-is 传递给 connect()

尝试更像这样的东西:

#include <winsock2.h>
#include <ws2tcpip.h>

#include <iostream>
#include <string>

#pragma comment(lib, "ws2_32.lib")

std::string addr_to_str(addrinfo *addr)
{
    char str[128];

    switch (addr->ai_family)
    {
        case AF_INET: // IPv4
        {
            if (inet_ntop(AF_INET, &(((sockaddr_in*)(addr->ai_addr))->sin_addr), str, INET_ADDRSTRLEN))
                return str;

            ret = WSAGetLastError();
            break;
        }

        case AF_INET6: // IPv6
        {
            if (inet_ntop(AF_INET6, &(((sockaddr_in6*)(addr->ai_addr))->sin6_addr), str, INET6_ADDRSTRLEN))
                return str;

            ret = WSAGetLastError();
            break;
        }

        default:
            ret = WSAEAFNOSUPPORT;
            break;
    }

    std::cerr << "inet_ntop error: " << ret << std::endl;
    return "";
}

int main()
{
    std::cout << "Simple_Client IPv4 & IPv6" << std::endl << std::endl;

    // Initiates Winsock
    WSADATA WSAData;
    int ret = WSAStartup(MAKEWORD(2, 0), &WSAData);
    if (ret != 0)
    {
        std::cerr << "WSAStartup error: " << ret << std::endl;
        return -1;
    }

    // Get Parameters IP/PORT and request
    std::string str_HOSTNAME = "mirror.neostrada.nl";
    int PORT = 21;

    // RESOLVE IP    
    addrinfo hints = { 0 };
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;

    addrinfo *pResult = NULL;
    ret = getaddrinfo(str_HOSTNAME.c_str(), std::to_string(PORT).c_str(), &hints, &pResult);
    if (ret != 0)
    {
        std::cerr << "getaddrinfo error: " << ret << std::endl;
        WSACleanup();
        return -1;
    }

    // Log the IPs
    bool has_IPv4 = false;
    bool has_IPv6 = false;

    for (addrinfo *addr = pResult; addr != NULL; addr = addr->ai_next)
    {
        switch (addr->ai_family)
        {
            case AF_INET: // IPv4
            {
                has_IPv4 = true;
                std::cout << "IPv4 : " << addr_to_str(addr);
                break;
            }

            case AF_INET6: // IPv6
            {
                has_IPv6 = true;
                std::cout << "IPv6 : " << addr_to_str(addr);
                break;
            }
        }
    }

    // Connect to the HOSTNAME
    SOCKET sock = INVALID_SOCKET;

    if (has_IPv6)
    {
        // try IPv6 first...
        for (addrinfo *addr = pResult; addr != NULL; addr = addr->ai_next)
        {
            if (addr->ai_family != AF_INET6)
                continue;

            std::cout << "Connecting to IPv6 : " << addr_to_str(addr) << " | Port : " << PORT << std::endl;

            sock = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
            if (sock == INVALID_SOCKET)
            {
                ret = WSAGetLastError();
                std::cerr << "socket error: " << ret << std::endl;
                continue;
            }

            if (connect(sock, addr->ai_addr, addr->ai_addrlen) == SOCKET_ERROR)
            {
                ret = WSAGetLastError();
                std::cerr << "connect error: " << ret << std::endl;
                closesocket(sock);
                sock = INVALID_SOCKET;
                continue;
            }

            break;
        }
    }

    if ((sock == INVALID_SOCKET) && (has_IPv4))
    {
        // try IPv4 next...
        for (addrinfo *addr = pResult; addr != NULL; addr = addr->ai_next)
        {
            if (addr->ai_family != AF_INET)
                continue;

            std::cout << "Connecting to IPv4 : " << addr_to_str(addr) << " | Port : " << PORT << std::endl;

            sock = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
            if (sock == INVALID_SOCKET)
            {
                ret = WSAGetLastError();
                std::cerr << "socket error: " << ret << std::endl;
                continue;
            }

            if (connect(sock, addr->ai_addr, addr->ai_addrlen) == SOCKET_ERROR)
            {
                ret = WSAGetLastError();
                std::cerr << "connect error: " << ret << std::endl;
                closesocket(sock);
                sock = INVALID_SOCKET;
                continue;
            }

            break;
        }
    }

    freeaddrinfo(pResult);

    if (sock == INVALID_SOCKET)
    {
        WSACleanup();
        return -2;
    }

    std::cout << "Connect Successful" << std::endl;

    char buf[1024];

    int size_recv = recv(sock, buf, sizeof(buf), 0);
    if (size_recv == SOCKET_ERROR)
    {
        ret = WSAGetLastError();
        std::cerr << "recv error: " << ret << std::endl;
    }
    else
    {
        std::cout << "SIZE RECV = " << size_recv;
        if (size_recv > 0)
        {
            std::cout << " | DATA RECV = ";
            std::cout.write(buf, size_recv);
        }
        std::cout << std::endl;
    }

    closesocket(sock);
    WSACleanup();

    std::cin.get();
    return 0;
}