我正在编写一些跨平台的网络代码,并且在WindowsLinux(WSL)上的getnameinfo()中遇到了一些不一致的行为。

下面的代码执行以下操作:


使用getaddrinfo()获取地址。
用以下地址在地址上呼叫getnameinfo()


NI_NAMEREQD已设置但未设置。
NI_NUMERICHOST已设置但未设置。





// INCLUDES

#if defined(PLATFORM_WINDOWS)

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

#else

#include <cerrno>
#include <netdb.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>

#endif

#include <algorithm>
#include <cstring>
#include <functional>
#include <iostream>
#include <memory>
#include <string>
#include <vector>


// DEBUG UTILS

namespace debug
{

#if defined(PLATFORM_WINDOWS)

    void die()
    {
        __debugbreak();
    }

#else

    void die()
    {
        raise(SIGTRAP);
    }

#endif

    void die_if(bool condition)
    {
        if (condition)
            die();
    }

} // debug


// NET CODE

enum class error_code
{
    no_error,

    host_not_found,
    try_again,
    out_of_memory,
    buffer_overflow,
    unrecoverable_error,
    system_error,
};

char const* get_error_string(error_code ec)
{
    switch (ec)
    {
    case error_code::no_error: return "no_error";
    case error_code::host_not_found: return "host_not_found";
    case error_code::try_again: return "try_again";
    case error_code::out_of_memory: return "out_of_memory";
    case error_code::buffer_overflow: return "buffer_overflow";
    case error_code::unrecoverable_error: return "unrecoverable_error";
    case error_code::system_error: return "system_error";
    }

    debug::die();
    return nullptr;
}

namespace ip
{

    enum class address_family
    {
        v4, v6, unspecified,
    };

    enum class protocol
    {
        tcp, udp,
    };

} // ip

class platform_context
{
public:

#if defined (PLATFORM_WINDOWS)
    platform_context()
    {
        auto data = WSADATA();
        auto const result = WSAStartup(MAKEWORD(2, 2), &data);
        debug::die_if(result != 0);
        debug::die_if(LOBYTE(data.wVersion) != 2 || HIBYTE(data.wVersion) != 2);
    }

    ~platform_context()
    {
        auto const result = WSACleanup();
        debug::die_if(result != 0);
    }

#else

    platform_context() { }
    ~platform_context() { }

#endif

    platform_context(platform_context const&) = delete;
    platform_context operator=(platform_context const&) = delete;
    platform_context(platform_context&&) = delete;
    platform_context operator=(platform_context&&) = delete;
};

using addrinfo_ptr = std::unique_ptr<addrinfo, std::function<void(addrinfo*)>>;

int get_ai_family(ip::address_family family)
{
    switch (family)
    {
    case ip::address_family::v4: return AF_INET;
    case ip::address_family::v6: return AF_INET6;
    case ip::address_family::unspecified: return AF_UNSPEC;
    }

    debug::die();
    return AF_UNSPEC;
}

int get_ai_socktype(ip::protocol protocol)
{
    switch (protocol)
    {
    case ip::protocol::tcp: return SOCK_STREAM;
    case ip::protocol::udp: return SOCK_DGRAM;
    }

    debug::die();
    return SOCK_STREAM;
}

int get_ai_protocol(ip::protocol protocol)
{
    switch (protocol)
    {
    case ip::protocol::tcp: return IPPROTO_TCP;
    case ip::protocol::udp: return IPPROTO_UDP;
    }

    debug::die();
    return IPPROTO_TCP;
}

ip::address_family get_ip_address_family(int ai_family)
{
    switch (ai_family)
    {
    case AF_INET: return ip::address_family::v4;
    case AF_INET6: return ip::address_family::v6;
    case AF_UNSPEC: return ip::address_family::unspecified;
    }

    debug::die();
    return ip::address_family::unspecified;
}

struct end_point
{
    explicit end_point(addrinfo const& info):
        address_length(0),
        address{ 0 }
    {
        debug::die_if(info.ai_addrlen < 0);
        debug::die_if(info.ai_addrlen > sizeof(sockaddr_storage));

        address_length = static_cast<std::size_t>(info.ai_addrlen);
        std::memcpy(&address, info.ai_addr, address_length);
    }

    ip::address_family get_address_family() const
    {
        return get_ip_address_family(address.ss_family);
    }

    std::size_t address_length;
    sockaddr_storage address;
};

std::vector<end_point> get_end_points(addrinfo_ptr const& info)
{
    if (!info)
        return {};

    auto result = std::vector<end_point>();

    auto ptr = info.get();
    while (ptr)
    {
        result.emplace_back(*ptr);
        ptr = ptr->ai_next;
    }

    return result;
}

addrinfo_ptr get_address(error_code&, char const* node, char const* service, ip::address_family family, ip::protocol protocol, int flags)
{
    debug::die_if(!node && !service);

    auto hints = addrinfo();
    std::memset(&hints, 0, sizeof(hints));
    hints.ai_family = get_ai_family(family);
    hints.ai_socktype = get_ai_socktype(protocol);
    hints.ai_protocol = get_ai_protocol(protocol);
    hints.ai_flags = flags;

    auto out = (addrinfo*) nullptr;
    auto const result = ::getaddrinfo(node, service, &hints, &out);

    // error handling ignored for this example
    // (make sure you have internet for testing remote end points)
    debug::die_if(result != 0);
    debug::die_if(out == nullptr);

    return addrinfo_ptr(out, std::bind(::freeaddrinfo, std::placeholders::_1));
}

std::vector<end_point> get_wildcard_address(error_code& ec, ip::address_family family, ip::protocol protocol)
{
    return get_end_points(get_address(ec, nullptr, "0", family, protocol, AI_PASSIVE));
}

std::vector<end_point> get_loopback_address(error_code& ec, ip::address_family family, ip::protocol protocol)
{
    return get_end_points(get_address(ec, nullptr, "0", family, protocol, 0));
}

std::vector<end_point> get_address(error_code& ec, std::string const& node, std::string const& service, ip::address_family family, ip::protocol protocol)
{
    return get_end_points(get_address(ec, node.c_str(), service.c_str(), family, protocol, 0));
};

enum class name_type
{
    numeric,
    name,
};

#if defined(PLATFORM_WINDOWS)

error_code get_getnameinfo_error(int result)
{
    debug::die_if(result == 0);

    auto const error = WSAGetLastError();

    debug::die_if(error == WSANOTINITIALISED);
    debug::die_if(error == WSAEAFNOSUPPORT);
    debug::die_if(error == WSAEINVAL);
    debug::die_if(error == WSAEFAULT);

    switch (error)
    {
    case WSAHOST_NOT_FOUND: return error_code::host_not_found;
    case WSATRY_AGAIN: return error_code::try_again;
    case WSA_NOT_ENOUGH_MEMORY: return error_code::out_of_memory;
    case WSANO_RECOVERY: return error_code::unrecoverable_error;
    }

    debug::die();
    return error_code::no_error;
}

std::size_t get_cstr_len(char const* string, std::size_t max)
{
    return strnlen_s(string, max);
}

#else

error_code get_getnameinfo_error(int result)
{
    debug::die_if(result == 0);

    auto const error = result;

    debug::die_if(error == EAI_FAMILY);
    debug::die_if(error == EAI_BADFLAGS);

    switch (error)
    {
    case EAI_NONAME: return error_code::host_not_found;
    case EAI_AGAIN: return error_code::try_again;
    case EAI_MEMORY: return error_code::out_of_memory;
    case EAI_OVERFLOW: return error_code::buffer_overflow;
    case EAI_FAIL: return error_code::unrecoverable_error;
    case EAI_SYSTEM: return error_code::system_error;
    }

    debug::die();
    return error_code::no_error;
}

std::size_t get_cstr_len(char const* string, std::size_t max)
{
    return strnlen(string, max);
}

#endif

//////////////

bool get_node_name(error_code& ec, std::string& node, name_type node_type, end_point const& end_point, bool require_name)
{
    auto const numeric_flag = (node_type == name_type::numeric ? NI_NUMERICHOST : 0);
    auto const require_flag = (require_name ? NI_NAMEREQD : 0);

    char node_buffer[NI_MAXHOST] = { 0 };
    auto const result = ::getnameinfo((sockaddr const*)&end_point.address, (socklen_t)end_point.address_length, node_buffer, NI_MAXHOST, nullptr, 0, numeric_flag | require_flag);

    if (result != 0)
    {
        ec = get_getnameinfo_error(result);
        return false;
    }

    node.resize(get_cstr_len(node_buffer, NI_MAXHOST));
    std::copy_n(node_buffer, node.size(), node.begin());

    return true;
}

//////////////

// TEST CODE

void test_get_node_name(end_point const& e, name_type node_type, bool name_required)
{
    auto ec = error_code::no_error;
    auto node = std::string();
    auto result = get_node_name(ec, node, node_type, e, name_required);

    std::cout << "\t"
        << (name_required ? "required - " : "not required - ")
        << (node_type == name_type::numeric ? "numeric - " : "");

    if (result)
        std::cout << "success (node name: '" << node << "')";
    else
        std::cout << "failed! (error: " << get_error_string(ec) << ")";

    std::cout << "\n";
}

int main()
{
    platform_context context;

    std::cout << "wildcard address:" << std::endl;
    {
        auto ec = error_code::no_error;
        auto end_points = get_wildcard_address(ec, ip::address_family::unspecified, ip::protocol::tcp);
        debug::die_if(end_points.empty());

        test_get_node_name(end_points.front(), name_type::name, true);
        test_get_node_name(end_points.front(), name_type::name, false);
        test_get_node_name(end_points.front(), name_type::numeric, true);
        test_get_node_name(end_points.front(), name_type::numeric, false);
    }

    std::cout << "loopback address:" << std::endl;
    {
        auto ec = error_code::no_error;
        auto end_points = get_loopback_address(ec, ip::address_family::unspecified, ip::protocol::tcp);
        debug::die_if(end_points.empty());

        test_get_node_name(end_points.front(), name_type::name, true);
        test_get_node_name(end_points.front(), name_type::name, false);
        test_get_node_name(end_points.front(), name_type::numeric, true);
        test_get_node_name(end_points.front(), name_type::numeric, false);
    }

    std::cout << "remote address:" << std::endl;
    {
        auto ec = error_code::no_error;
        auto end_points = get_address(ec, "www.google.com", "443", ip::address_family::unspecified, ip::protocol::tcp);
        debug::die_if(end_points.empty());

        test_get_node_name(end_points.front(), name_type::name, true);
        test_get_node_name(end_points.front(), name_type::name, false);
        test_get_node_name(end_points.front(), name_type::numeric, true);
        test_get_node_name(end_points.front(), name_type::numeric, false);
    }
}


可以在Windows上使用cl main.cpp /DPLATFORM_WINDOWS /nologo /EHsc /W4 /WX ws2_32.lib在WSL上使用g++ -Wall -Werror -std=c++17 -o main main.cpp进行编译。

我在系统上得到以下输出:

视窗:

wildcard address:
        required - success (node name: 'ComputerName')
        not required - success (node name: 'ComputerName')
        required - numeric - success (node name: '::')
        not required - numeric - success (node name: '::')
loopback address:
        required - success (node name: 'ComputerName')
        not required - success (node name: 'ComputerName')
        required - numeric - success (node name: '::1')
        not required - numeric - success (node name: '::1')
remote address:
        required - success (node name: 'lhr25s12-in-f4.1e100.net')
        not required - success (node name: 'lhr25s12-in-f4.1e100.net')
        required - numeric - success (node name: '216.58.204.36')
        not required - numeric - success (node name: '216.58.204.36')


WSL:

wildcard address:
        required - failed! (error: host_not_found)
        not required - success (node name: '0.0.0.0')
        required - numeric - failed! (error: host_not_found)
        not required - numeric - success (node name: '0.0.0.0')
loopback address:
        required - success (node name: 'ip6-localhost')
        not required - success (node name: 'ip6-localhost')
        required - numeric - failed! (error: host_not_found)
        not required - numeric - success (node name: '::1')
remote address:
        required - success (node name: 'lhr25s12-in-x04.1e100.net')
        not required - success (node name: 'lhr25s12-in-x04.1e100.net')
        required - numeric - failed! (error: host_not_found)
        not required - numeric - success (node name: '2a00:1450:4009:80d::2004')


因此,getnameinfo()行为差异为:


非数字通配符地址在Windows上有效,但在WSL上失败。
设置NI_NAMEREQD时,WSL上的数字地址查找失败。


这些差异仅仅是规格的替代解释吗? Windows版本将ComputerName作为主机名返回是否合理?

最佳答案

我还不确定为什么通配符查找失败,但是在glibc中进行挖掘之后,看来NI_NUMERICHOSTNI_NAMEREQD根本无法协同工作:

/* Convert AF_INET or AF_INET6 socket address, host part.  */
static int
gni_host_inet (struct scratch_buffer *tmpbuf,
           const struct sockaddr *sa, socklen_t addrlen,
           char *host, socklen_t hostlen, int flags)
{
  if (!(flags & NI_NUMERICHOST))
    {
      int result = gni_host_inet_name
    (tmpbuf, sa, addrlen, host, hostlen, flags);
      if (result != EAI_NONAME)
    return result;
    }

  if (flags & NI_NAMEREQD)
    return EAI_NONAME;
  else
    return gni_host_inet_numeric
      (tmpbuf, sa, addrlen, host, hostlen, flags);
}

关于c++ - WSL上的`getnameinfo()`失败,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/57928275/

10-10 11:59