Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2925305b authored by waynema's avatar waynema
Browse files

Support testing for DNS Transport over TCP

a. Add a large payload packet (over 512 bytes) with OPT padding for sending DNS
 query over TCP.
b. Create TCP socket and TCP response handle function in dns_responder.
c. Add GetNumQueriesForProtocol(protocol) to get the number of queries on
 TCP/UDP.

Test: cd packages/modules/DnsResolver && atest
Change-Id: I7c6926502d200980a77f47a3df057f30587521f8
parent c727677f
Loading
Loading
Loading
Loading
+163 −59
Original line number Diff line number Diff line
@@ -141,6 +141,17 @@ const char* dnsclass2str(unsigned dnsclass) {
    return it->second;
}

const char* dnsproto2str(int protocol) {
    switch (protocol) {
        case IPPROTO_TCP:
            return "TCP";
        case IPPROTO_UDP:
            return "UDP";
        default:
            return "UNKNOWN";
    }
}

const char* DNSName::read(const char* buffer, const char* buffer_end) {
    const char* cur = buffer;
    bool last = false;
@@ -507,7 +518,7 @@ void DNSResponder::setEdns(Edns edns) {
}

bool DNSResponder::running() const {
    return socket_.get() != -1;
    return (udp_socket_.ok()) && (tcp_socket_.ok());
}

bool DNSResponder::startServer() {
@@ -516,59 +527,51 @@ bool DNSResponder::startServer() {
        return false;
    }

    // Set up UDP socket.
    addrinfo ai_hints{
            .ai_flags = AI_PASSIVE,
            .ai_family = AF_UNSPEC,
            .ai_socktype = SOCK_DGRAM,
    };
    addrinfo* ai_res = nullptr;
    int rv = getaddrinfo(listen_address_.c_str(), listen_service_.c_str(), &ai_hints, &ai_res);
    ScopedAddrinfo ai_res_cleanup(ai_res);
    if (rv) {
        LOG(ERROR) << "getaddrinfo(" << listen_address_ << ", " << listen_service_
                   << ") failed: " << gai_strerror(rv);
    // Create UDP, TCP socket
    if (udp_socket_ = createListeningSocket(SOCK_DGRAM); udp_socket_.get() < 0) {
        PLOG(ERROR) << "failed to create UDP socket";
        return false;
    }
    for (const addrinfo* ai = ai_res; ai; ai = ai->ai_next) {
        socket_.reset(socket(ai->ai_family, ai->ai_socktype | SOCK_NONBLOCK, ai->ai_protocol));
        if (socket_.get() < 0) {
            PLOG(INFO) << "ignore creating socket " << socket_.get() << " failed";
            continue;
        }
        enableSockopt(socket_.get(), SOL_SOCKET, SO_REUSEPORT).ignoreError();
        enableSockopt(socket_.get(), SOL_SOCKET, SO_REUSEADDR).ignoreError();
        std::string host_str = addr2str(ai->ai_addr, ai->ai_addrlen);
        if (bind(socket_.get(), ai->ai_addr, ai->ai_addrlen)) {
            LOG(INFO) << "failed to bind UDP " << host_str << ":" << listen_service_;
            continue;

    if (tcp_socket_ = createListeningSocket(SOCK_STREAM); tcp_socket_.get() < 0) {
        PLOG(ERROR) << "failed to create TCP socket";
        return false;
    }
        LOG(INFO) << "bound to UDP " << host_str << ":" << listen_service_;
        break;

    if (listen(tcp_socket_.get(), 1) < 0) {
        PLOG(ERROR) << "failed to listen TCP socket";
        return false;
    }

    // Set up eventfd socket.
    event_fd_.reset(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC));
    if (event_fd_.get() == -1) {
        PLOG(ERROR) << "failed to create eventfd " << event_fd_.get();
        PLOG(ERROR) << "failed to create eventfd";
        return false;
    }

    // Set up epoll socket.
    epoll_fd_.reset(epoll_create1(EPOLL_CLOEXEC));
    if (epoll_fd_.get() < 0) {
        PLOG(ERROR) << "epoll_create1() failed on fd " << epoll_fd_.get();
        PLOG(ERROR) << "epoll_create1() failed on fd";
        return false;
    }

    LOG(INFO) << "adding UDP socket to epoll";
    if (!addFd(udp_socket_.get(), EPOLLIN)) {
        LOG(ERROR) << "failed to add the UDP socket to epoll";
        return false;
    }

    LOG(INFO) << "adding socket " << socket_.get() << " to epoll";
    if (!addFd(socket_.get(), EPOLLIN)) {
        LOG(ERROR) << "failed to add the socket " << socket_.get() << " to epoll";
    LOG(INFO) << "adding TCP socket to epoll";
    if (!addFd(tcp_socket_.get(), EPOLLIN)) {
        LOG(ERROR) << "failed to add the TCP socket to epoll";
        return false;
    }
    LOG(INFO) << "adding eventfd " << event_fd_.get() << " to epoll";

    LOG(INFO) << "adding eventfd to epoll";
    if (!addFd(event_fd_.get(), EPOLLIN)) {
        LOG(ERROR) << "failed to add the eventfd " << event_fd_.get() << " to epoll";
        LOG(ERROR) << "failed to add the eventfd to epoll";
        return false;
    }

@@ -592,12 +595,14 @@ bool DNSResponder::stopServer() {
    }
    handler_thread_.join();
    epoll_fd_.reset();
    socket_.reset();
    event_fd_.reset();
    udp_socket_.reset();
    tcp_socket_.reset();
    LOG(INFO) << "server stopped successfully";
    return true;
}

std::vector<std::pair<std::string, ns_type>> DNSResponder::queries() const {
std::vector<DNSResponder::QueryInfo> DNSResponder::queries() const {
    std::lock_guard lock(queries_mutex_);
    return queries_;
}
@@ -605,8 +610,10 @@ std::vector<std::pair<std::string, ns_type>> DNSResponder::queries() const {
std::string DNSResponder::dumpQueries() const {
    std::lock_guard lock(queries_mutex_);
    std::string out;

    for (const auto& q : queries_) {
        out += "{\"" + q.first + "\", " + std::to_string(q.second) + "} ";
        out += "{\"" + q.name + "\", " + std::to_string(q.type) + "\", " +
               dnsproto2str(q.protocol) + "} ";
    }
    return out;
}
@@ -631,8 +638,10 @@ void DNSResponder::requestHandler() {
            if (fd == event_fd_.get() && (events & (EPOLLIN | EPOLLERR))) {
                handleEventFd();
                return;
            } else if (fd == socket_.get() && (events & (EPOLLIN | EPOLLERR))) {
                handleQuery();
            } else if (fd == udp_socket_.get() && (events & (EPOLLIN | EPOLLERR))) {
                handleQuery(IPPROTO_UDP);
            } else if (fd == tcp_socket_.get() && (events & (EPOLLIN | EPOLLERR))) {
                handleQuery(IPPROTO_TCP);
            } else {
                LOG(WARNING) << "unexpected epoll events " << events << " on fd " << fd;
            }
@@ -640,9 +649,9 @@ void DNSResponder::requestHandler() {
    }
}

bool DNSResponder::handleDNSRequest(const char* buffer, ssize_t len, char* response,
bool DNSResponder::handleDNSRequest(const char* buffer, ssize_t len, int protocol, char* response,
                                    size_t* response_len) const {
    LOG(DEBUG) << "request: '" << str2hex(buffer, len) << "'";
    LOG(DEBUG) << "request: '" << str2hex(buffer, len) << "', on " << dnsproto2str(protocol);
    const char* buffer_end = buffer + len;
    DNSHeader header;
    const char* cur = header.read(buffer, buffer_end);
@@ -686,10 +695,9 @@ bool DNSResponder::handleDNSRequest(const char* buffer, ssize_t len, char* respo
    {
        std::lock_guard lock(queries_mutex_);
        for (const DNSQuestion& question : header.questions) {
            queries_.push_back(make_pair(question.qname.name, ns_type(question.qtype)));
            queries_.push_back({question.qname.name, ns_type(question.qtype), protocol});
        }
    }

    // Ignore requests with the preset probability.
    auto constexpr bound = std::numeric_limits<unsigned>::max();
    if (arc4random_uniform(bound) > bound * response_probability_) {
@@ -951,36 +959,94 @@ bool DNSResponder::addFd(int fd, uint32_t events) {
    return true;
}

void DNSResponder::handleQuery() {
void DNSResponder::handleQuery(int protocol) {
    char buffer[4096];
    sockaddr_storage sa;
    socklen_t sa_len = sizeof(sa);
    ssize_t len;
    ssize_t len = 0;
    android::base::unique_fd tcpFd;
    switch (protocol) {
        case IPPROTO_UDP:
            do {
        len = recvfrom(socket_.get(), buffer, sizeof(buffer), 0, (sockaddr*)&sa, &sa_len);
                len = recvfrom(udp_socket_.get(), buffer, sizeof(buffer), 0, (sockaddr*)&sa,
                               &sa_len);
            } while (len < 0 && (errno == EAGAIN || errno == EINTR));
            if (len <= 0) {
        PLOG(INFO) << "recvfrom() failed, len=" << len;
                PLOG(ERROR) << "recvfrom() failed, len=" << len;
                return;
            }
            break;
        case IPPROTO_TCP:
            tcpFd.reset(accept4(tcp_socket_.get(), reinterpret_cast<sockaddr*>(&sa), &sa_len,
                                SOCK_CLOEXEC));
            if (tcpFd.get() < 0) {
                PLOG(ERROR) << "failed to accept client socket";
                return;
            }
            // Get the message length from two byte length field.
            // See also RFC 1035, section 4.2.2 and RFC 7766, section 8
            uint8_t queryMessageLengthField[2];
            if (read(tcpFd.get(), &queryMessageLengthField, 2) != 2) {
                PLOG(ERROR) << "Not enough length field bytes";
                return;
            }
    LOG(DEBUG) << "read " << len << " bytes";

            const uint16_t qlen = (queryMessageLengthField[0] << 8) | queryMessageLengthField[1];
            while (len < qlen) {
                ssize_t ret = read(tcpFd.get(), buffer + len, qlen - len);
                if (ret <= 0) {
                    PLOG(ERROR) << "Error while reading query";
                    return;
                }
                len += ret;
            }
            break;
    }
    LOG(DEBUG) << "read " << len << " bytes on " << dnsproto2str(protocol);
    std::lock_guard lock(cv_mutex_);
    char response[4096];
    size_t response_len = sizeof(response);
    if (handleDNSRequest(buffer, len, response, &response_len) && response_len > 0) {
    // TODO: check whether sending malformed packets to DnsResponder
    if (handleDNSRequest(buffer, len, protocol, response, &response_len) && response_len > 0) {
        // place wait_for after handleDNSRequest() so we can check the number of queries in
        // test case before it got responded.
        std::unique_lock guard(cv_mutex_for_deferred_resp_);
        cv_for_deferred_resp_.wait(
                guard, [this]() REQUIRES(cv_mutex_for_deferred_resp_) { return !deferred_resp_; });
        len = 0;

        len = sendto(socket_.get(), response, response_len, 0,
        switch (protocol) {
            case IPPROTO_UDP:
                len = sendto(udp_socket_.get(), response, response_len, 0,
                             reinterpret_cast<const sockaddr*>(&sa), sa_len);
        std::string host_str = addr2str(reinterpret_cast<const sockaddr*>(&sa), sa_len);
                if (len < 0) {
                    PLOG(ERROR) << "Failed to send response";
                }
                break;
            case IPPROTO_TCP:
                // Get the message length from two byte length field.
                // See also RFC 1035, section 4.2.2 and RFC 7766, section 8
                uint8_t responseMessageLengthField[2];
                responseMessageLengthField[0] = response_len >> 8;
                responseMessageLengthField[1] = response_len;
                if (write(tcpFd.get(), responseMessageLengthField, 2) != 2) {
                    PLOG(ERROR) << "Failed to write response length field";
                    break;
                }
                if (write(tcpFd.get(), response, response_len) !=
                    static_cast<ssize_t>(response_len)) {
                    PLOG(ERROR) << "Failed to write response";
                    break;
                }
                len = response_len;
                break;
        }
        const std::string host_str = addr2str(reinterpret_cast<const sockaddr*>(&sa), sa_len);
        if (len > 0) {
            LOG(DEBUG) << "sent " << len << " bytes to " << host_str;
        } else {
            PLOG(INFO) << "sendto() failed for " << host_str;
            const char* method_str = (protocol == IPPROTO_TCP) ? "write()" : "sendto()";
            LOG(ERROR) << method_str << " failed for " << host_str;
        }
        // Test that the response is actually a correct DNS message.
        // TODO: Make DNS message test to support name compression. Or it throws a warning for
@@ -1012,4 +1078,42 @@ void DNSResponder::handleEventFd() {
    }
}

android::base::unique_fd DNSResponder::createListeningSocket(int socket_type) {
    addrinfo ai_hints{
            .ai_flags = AI_PASSIVE,
            .ai_family = AF_UNSPEC,
            .ai_socktype = socket_type,
    };
    addrinfo* ai_res = nullptr;
    const int rv =
            getaddrinfo(listen_address_.c_str(), listen_service_.c_str(), &ai_hints, &ai_res);
    ScopedAddrinfo ai_res_cleanup(ai_res);
    if (rv) {
        LOG(ERROR) << "getaddrinfo(" << listen_address_ << ", " << listen_service_
                   << ") failed: " << gai_strerror(rv);
        return {};
    }
    for (const addrinfo* ai = ai_res; ai; ai = ai->ai_next) {
        android::base::unique_fd fd(
                socket(ai->ai_family, ai->ai_socktype | SOCK_NONBLOCK, ai->ai_protocol));
        if (fd.get() < 0) {
            PLOG(ERROR) << "ignore creating socket failed";
            continue;
        }
        enableSockopt(fd.get(), SOL_SOCKET, SO_REUSEPORT).ignoreError();
        enableSockopt(fd.get(), SOL_SOCKET, SO_REUSEADDR).ignoreError();
        const std::string host_str = addr2str(ai->ai_addr, ai->ai_addrlen);
        const char* socket_str = (socket_type == SOCK_STREAM) ? "TCP" : "UDP";

        if (bind(fd.get(), ai->ai_addr, ai->ai_addrlen)) {
            PLOG(ERROR) << "failed to bind " << socket_str << " " << host_str << ":"
                        << listen_service_;
            continue;
        }
        LOG(INFO) << "bound to " << socket_str << " " << host_str << ":" << listen_service_;
        return fd;
    }
    return {};
}

}  // namespace test
+18 −8
Original line number Diff line number Diff line
@@ -123,7 +123,7 @@ inline const ns_rcode kDefaultErrorCode = ns_rcode::ns_r_servfail;
 */
class DNSResponder {
  public:
    enum class Edns : uint8_t {
    enum class Edns {
        ON,
        FORMERR_ON_EDNS,  // DNS server not supporting EDNS will reply FORMERR.
        FORMERR_UNCOND,   // DNS server reply FORMERR unconditionally
@@ -133,12 +133,18 @@ class DNSResponder {
    // See also addMapping{, DnsHeader, BinaryPacket}, removeMapping{, DnsHeader, BinaryPacket},
    // makeResponse{, FromDnsHeader, FromBinaryPacket}.
    // TODO: Perhaps break class DNSResponder for each mapping.
    enum class MappingType : uint8_t {
    enum class MappingType {
        ADDRESS_OR_HOSTNAME,  // Use the mapping from (name, type) to (address or hostname)
        DNS_HEADER,           // Use the mapping from (name, type) to (DNSHeader)
        BINARY_PACKET,        // Use the mapping from (query packet) to (response packet)
    };

    struct QueryInfo {
        std::string name;
        ns_type type;
        int protocol;  // Either IPPROTO_TCP or IPPROTO_UDP
    };

    DNSResponder(std::string listen_address = kDefaultListenAddr,
                 std::string listen_service = kDefaultListenService,
                 ns_rcode error_rcode = kDefaultErrorCode,
@@ -169,7 +175,7 @@ class DNSResponder {
    bool stopServer();
    const std::string& listen_address() const { return listen_address_; }
    const std::string& listen_service() const { return listen_service_; }
    std::vector<std::pair<std::string, ns_type>> queries() const;
    std::vector<QueryInfo> queries() const;
    std::string dumpQueries() const;
    void clearQueries();
    std::condition_variable& getCv() { return cv; }
@@ -221,7 +227,7 @@ class DNSResponder {
    // Parses and generates a response message for incoming DNS requests.
    // Returns false to ignore the request, which might be due to either parsing error
    // or unresponsiveness.
    bool handleDNSRequest(const char* buffer, ssize_t buffer_len, char* response,
    bool handleDNSRequest(const char* buffer, ssize_t buffer_len, int protocol, char* response,
                          size_t* response_len) const;

    bool addAnswerRecords(const DNSQuestion& question, std::vector<DNSRecord>* answers) const;
@@ -246,7 +252,7 @@ class DNSResponder {

    // Read the query sent from the client and send the answer back to the client. It
    // makes sure the I/O communicated with the client is correct.
    void handleQuery();
    void handleQuery(int protocol);

    // Trigger the handler thread to terminate.
    bool sendToEventFd();
@@ -254,7 +260,10 @@ class DNSResponder {
    // Used in the handler thread for the termination signal.
    void handleEventFd();

    // Address and service to listen on, currently limited to UDP.
    // TODO: Move createListeningSocket to resolv_test_utils.h
    android::base::unique_fd createListeningSocket(int socket_type);

    // Address and service to listen on TCP and UDP.
    const std::string listen_address_;
    const std::string listen_service_;
    // Error code to return for requests for an unknown name.
@@ -287,10 +296,11 @@ class DNSResponder {

    mutable std::mutex mappings_mutex_;
    // Query names received so far and the corresponding mutex.
    mutable std::vector<std::pair<std::string, ns_type>> queries_ GUARDED_BY(queries_mutex_);
    mutable std::vector<QueryInfo> queries_ GUARDED_BY(queries_mutex_);
    mutable std::mutex queries_mutex_;
    // Socket on which the server is listening.
    android::base::unique_fd socket_;
    android::base::unique_fd udp_socket_;
    android::base::unique_fd tcp_socket_;
    // File descriptor for epoll.
    android::base::unique_fd epoll_fd_;
    // Eventfd used to signal for the handler thread termination.
+38 −2
Original line number Diff line number Diff line
@@ -1377,8 +1377,8 @@ TEST_F(ResolverTest, GetHostByName_Tls) {
    EXPECT_EQ("1.2.3.2", ToString(result));
    const auto queries = dns.queries();
    EXPECT_EQ(1U, queries.size());
    EXPECT_EQ("tls2.example.com.", queries[0].first);
    EXPECT_EQ(ns_t_a, queries[0].second);
    EXPECT_EQ("tls2.example.com.", queries[0].name);
    EXPECT_EQ(ns_t_a, queries[0].type);

    // Reset the resolvers without enabling TLS.  Queries should still be routed
    // to the UDP endpoint.
@@ -3719,6 +3719,42 @@ TEST_F(ResolverTest, FlushNetworkCache_concurrent) {
    EXPECT_EQ(kHelloExampleComAddrV4, ToString(result));
}

// TODO: Perhaps to have a boundary conditions test for TCP and UDP.
TEST_F(ResolverTest, TcpQueryWithOversizePayload) {
    test::DNSResponder dns;
    StartDns(dns, {{kHelloExampleCom, ns_type::ns_t_a, kHelloExampleComAddrV4}});
    ASSERT_TRUE(mDnsClient.SetResolversForNetwork());

    int fd = dns_open_proxy();
    ASSERT_TRUE(fd > 0);

    // Sending DNS query over TCP once the packet sizes exceed 512 bytes.
    // The raw data is combined with Question section and Additional section
    // Question section : query "hello.example.com", type A, class IN
    // Additional section : type OPT (41), Option PADDING, Option Length 546
    // Padding option which allows DNS clients and servers to artificially
    // increase the size of a DNS message by a variable number of bytes.
    // See also RFC7830, section 3
    const std::string query =
            "+c0BAAABAAAAAAABBWhlbGxvB2V4YW1wbGUDY29tAAABAAEAACkgAAAAgAACJgAMAiIAAAAAAAAAAAAAAAAAA"
            "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
            "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
            "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
            "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
            "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
            "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
            "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
            "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
            "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
    const std::string cmd =
            "resnsend " + std::to_string(TEST_NETID) + " 0 " /* ResNsendFlags */ + query + '\0';
    ssize_t rc = TEMP_FAILURE_RETRY(write(fd, cmd.c_str(), cmd.size()));
    EXPECT_EQ(rc, static_cast<ssize_t>(cmd.size()));
    expectAnswersValid(fd, AF_INET, kHelloExampleComAddrV4);
    EXPECT_EQ(1U, GetNumQueriesForProtocol(dns, IPPROTO_TCP, kHelloExampleCom));
    EXPECT_EQ(0U, GetNumQueriesForProtocol(dns, IPPROTO_UDP, kHelloExampleCom));
}

// Parameterized tests.
// TODO: Merge the existing tests as parameterized test if possible.
// TODO: Perhaps move parameterized tests to an independent file.
+16 −4
Original line number Diff line number Diff line
@@ -105,10 +105,22 @@ std::vector<std::string> ToStrings(const ScopedAddrinfo& ai) {
}

size_t GetNumQueries(const test::DNSResponder& dns, const char* name) {
    auto queries = dns.queries();
    std::vector<test::DNSResponder::QueryInfo> queries = dns.queries();
    size_t found = 0;
    for (const auto& p : queries) {
        if (p.first == name) {
        if (p.name == name) {
            ++found;
        }
    }
    return found;
}

size_t GetNumQueriesForProtocol(const test::DNSResponder& dns, const int protocol,
                                const char* name) {
    std::vector<test::DNSResponder::QueryInfo> queries = dns.queries();
    size_t found = 0;
    for (const auto& p : queries) {
        if (p.protocol == protocol && p.name == name) {
            ++found;
        }
    }
@@ -116,10 +128,10 @@ size_t GetNumQueries(const test::DNSResponder& dns, const char* name) {
}

size_t GetNumQueriesForType(const test::DNSResponder& dns, ns_type type, const char* name) {
    auto queries = dns.queries();
    std::vector<test::DNSResponder::QueryInfo> queries = dns.queries();
    size_t found = 0;
    for (const auto& p : queries) {
        if (p.second == type && p.first == name) {
        if (p.type == type && p.name == name) {
            ++found;
        }
    }
+3 −0
Original line number Diff line number Diff line
@@ -101,7 +101,10 @@ static const test::DNSHeader kDefaultDnsHeader = {
        .ad = false,            // non-authenticated data is unacceptable
};

// TODO: Integrate GetNumQueries relevent functions
size_t GetNumQueries(const test::DNSResponder& dns, const char* name);
size_t GetNumQueriesForProtocol(const test::DNSResponder& dns, const int protocol,
                                const char* name);
size_t GetNumQueriesForType(const test::DNSResponder& dns, ns_type type, const char* name);
std::string ToString(const hostent* he);
std::string ToString(const addrinfo* ai);