Loading tests/dns_responder/dns_responder.cpp +163 −59 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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() { Loading @@ -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; } Loading @@ -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_; } Loading @@ -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; } Loading @@ -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; } Loading @@ -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); Loading Loading @@ -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_) { Loading Loading @@ -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 Loading Loading @@ -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 tests/dns_responder/dns_responder.h +18 −8 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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, Loading Loading @@ -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; } Loading Loading @@ -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; Loading @@ -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(); Loading @@ -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. Loading Loading @@ -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. Loading tests/resolv_integration_test.cpp +38 −2 Original line number Diff line number Diff line Loading @@ -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. Loading Loading @@ -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. Loading tests/resolv_test_utils.cpp +16 −4 Original line number Diff line number Diff line Loading @@ -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; } } Loading @@ -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; } } Loading tests/resolv_test_utils.h +3 −0 Original line number Diff line number Diff line Loading @@ -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); Loading Loading
tests/dns_responder/dns_responder.cpp +163 −59 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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() { Loading @@ -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; } Loading @@ -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_; } Loading @@ -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; } Loading @@ -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; } Loading @@ -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); Loading Loading @@ -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_) { Loading Loading @@ -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 Loading Loading @@ -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
tests/dns_responder/dns_responder.h +18 −8 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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, Loading Loading @@ -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; } Loading Loading @@ -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; Loading @@ -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(); Loading @@ -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. Loading Loading @@ -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. Loading
tests/resolv_integration_test.cpp +38 −2 Original line number Diff line number Diff line Loading @@ -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. Loading Loading @@ -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. Loading
tests/resolv_test_utils.cpp +16 −4 Original line number Diff line number Diff line Loading @@ -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; } } Loading @@ -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; } } Loading
tests/resolv_test_utils.h +3 −0 Original line number Diff line number Diff line Loading @@ -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); Loading