Loading DnsProxyListener.cpp +108 −19 Original line number Diff line number Diff line Loading @@ -469,7 +469,7 @@ void logDnsQueryResult(const addrinfo* res) { NI_NUMERICHOST); if (!ret) { LOG(DEBUG) << __func__ << ": [" << i << "] " << ai->ai_flags << " " << ai->ai_family << " " << ai->ai_socktype << " " << ai->ai_protocol; << " " << ai->ai_socktype << " " << ai->ai_protocol << " " << ip_addr; } else { LOG(DEBUG) << __func__ << ": [" << i << "] numeric hostname translation fail " << ret; } Loading Loading @@ -523,30 +523,95 @@ bool synthesizeNat64PrefixWithARecord(const netdutils::IPPrefix& prefix, struct return true; } bool synthesizeNat64PrefixWithARecord(const netdutils::IPPrefix& prefix, addrinfo* result) { if (result == nullptr) return false; if (!onlyNonSpecialUseIPv4Addresses(result)) return false; bool synthesizeNat64PrefixWithARecord(const netdutils::IPPrefix& prefix, addrinfo** res, bool unspecWantedButNoIPv6) { if (*res == nullptr) return false; if (!onlyNonSpecialUseIPv4Addresses(*res)) return false; if (!isValidNat64Prefix(prefix)) return false; struct sockaddr_storage ss = netdutils::IPSockAddr(prefix.ip()); struct sockaddr_in6* v6prefix = (struct sockaddr_in6*)&ss; for (addrinfo* ai = result; ai; ai = ai->ai_next) { struct sockaddr_in sinOriginal = *(struct sockaddr_in*)ai->ai_addr; struct sockaddr_in6* sin6 = (struct sockaddr_in6*)ai->ai_addr; memset(sin6, 0, sizeof(sockaddr_in6)); const sockaddr_storage ss = netdutils::IPSockAddr(prefix.ip()); const sockaddr_in6* v6prefix = (sockaddr_in6*)&ss; addrinfo* const head4 = *res; addrinfo* head6 = nullptr; addrinfo* cur6 = nullptr; // Build a synthesized AAAA addrinfo list from the queried A addrinfo list. Here is the diagram // for the relationship of pointers. // // head4: point to the first queried A addrinfo // | // v // +-------------+ +-------------+ // | addrinfo4#1 |-->| addrinfo4#2 |--> .. queried A addrinfo(s) for DNS64 synthesis // +-------------+ +-------------+ // ^ // | // cur4: current worked-on queried A addrinfo // // head6: point to the first synthesized AAAA addrinfo // | // v // +-------------+ +-------------+ // | addrinfo6#1 |-->| addrinfo6#2 |--> .. synthesized DNS64 AAAA addrinfo(s) // +-------------+ +-------------+ // ^ // | // cur6: current worked-on synthesized addrinfo // for (const addrinfo* cur4 = head4; cur4; cur4 = cur4->ai_next) { // Allocate a space for a synthesized AAAA addrinfo. Note that the addrinfo and sockaddr // occupy one contiguous block of memory and are allocated and freed as a single block. // See get_ai and freeaddrinfo in packages/modules/DnsResolver/getaddrinfo.cpp. addrinfo* sa = (addrinfo*)calloc(1, sizeof(addrinfo) + sizeof(sockaddr_in6)); if (sa == nullptr) { LOG(ERROR) << "allocate memory failed for synthesized result"; freeaddrinfo(head6); return false; } // Initialize the synthesized AAAA addrinfo by the queried A addrinfo. The ai_addr will be // set lately. sa->ai_flags = cur4->ai_flags; sa->ai_family = AF_INET6; sa->ai_socktype = cur4->ai_socktype; sa->ai_protocol = cur4->ai_protocol; sa->ai_addrlen = sizeof(sockaddr_in6); sa->ai_addr = (sockaddr*)(sa + 1); sa->ai_canonname = nullptr; sa->ai_next = nullptr; if (cur4->ai_canonname != nullptr) { sa->ai_canonname = strdup(cur4->ai_canonname); if (sa->ai_canonname == nullptr) { LOG(ERROR) << "allocate memory failed for canonname"; freeaddrinfo(sa); freeaddrinfo(head6); return false; } } // Synthesize /96 NAT64 prefix in place. The space has reserved by get_ai() in // system/netd/resolv/getaddrinfo.cpp. // Synthesize /96 NAT64 prefix with the queried IPv4 address. const sockaddr_in* sin4 = (sockaddr_in*)cur4->ai_addr; sockaddr_in6* sin6 = (sockaddr_in6*)sa->ai_addr; sin6->sin6_addr = v6prefix->sin6_addr; sin6->sin6_addr.s6_addr32[3] = sinOriginal.sin_addr.s_addr; sin6->sin6_addr.s6_addr32[3] = sin4->sin_addr.s_addr; sin6->sin6_family = AF_INET6; sin6->sin6_port = sinOriginal.sin_port; ai->ai_addrlen = sizeof(struct sockaddr_in6); ai->ai_family = AF_INET6; sin6->sin6_port = sin4->sin_port; // If the synthesized list is empty, this becomes the first element. if (head6 == nullptr) { head6 = sa; } // Add this element to the end of the synthesized list. if (cur6 != nullptr) { cur6->ai_next = sa; } cur6 = sa; if (WOULD_LOG(VERBOSE)) { char buf[INET6_ADDRSTRLEN]; // big enough for either IPv4 or IPv6 inet_ntop(AF_INET, &sinOriginal.sin_addr.s_addr, buf, sizeof(buf)); inet_ntop(AF_INET, &sin4->sin_addr.s_addr, buf, sizeof(buf)); LOG(VERBOSE) << __func__ << ": DNS A record: " << buf; inet_ntop(AF_INET6, &v6prefix->sin6_addr, buf, sizeof(buf)); LOG(VERBOSE) << __func__ << ": NAT64 prefix: " << buf; Loading @@ -554,7 +619,31 @@ bool synthesizeNat64PrefixWithARecord(const netdutils::IPPrefix& prefix, addrinf LOG(VERBOSE) << __func__ << ": DNS64 Synthesized AAAA record: " << buf; } } logDnsQueryResult(result); // Simply concatenate the synthesized AAAA addrinfo list and the queried A addrinfo list when // AF_UNSPEC is specified. Prefer that synthesized IPv6 addresses have higher precedence than // IPv4 addresses because having a NAT64 prefix implies that there is an IPv6-only network. // Note that head6 and cur6 should be non-null because there was at least one IPv4 address // synthesized. // TODO: Sort the concatenated addresses by RFC 6724 section 2.1. Note that the // socket type and protocol may be considered as well. Currently, resolv_getaddrinfo() calls // explore_fqdn() times by the different items of explore_options. It means that // _rfc6724_sort() only sorts the results in each explore_options and concatenates each results // into one. For example, getaddrinfo() is called with null hints for a domain name which has // both IPv4 and IPv6 addresses. The address order of the result addrinfo may be: // 2001:db8::102:304 (socktype=2, protocol=17) -> 1.2.3.4 (socktype=2, protocol=17) -> // 2001:db8::102:304 (socktype=1, protocol=6) -> 1.2.3.4 (socktype=1, protocol=6) // In above example, the first two results come from one explore option and the last two come // from another one. They are sorted first, and then concatenate together to be the result. // See also resolv_getaddrinfo in packages/modules/DnsResolver/getaddrinfo.cpp. if (unspecWantedButNoIPv6) { cur6->ai_next = head4; } else { freeaddrinfo(head4); } *res = head6; logDnsQueryResult(*res); return true; } Loading Loading @@ -713,7 +802,7 @@ void DnsProxyListener::GetAddrInfoHandler::doDns64Synthesis(int32_t* rv, addrinf } } if (!synthesizeNat64PrefixWithARecord(prefix, *res)) { if (!synthesizeNat64PrefixWithARecord(prefix, res, unspecWantedButNoIPv6)) { if (ipv6WantedButNoData) { // If caller wants IPv6 answers but no data and failed to synthesize IPv6 answers, // don't return the IPv4 answers. Loading tests/resolv_integration_test.cpp +14 −12 Original line number Diff line number Diff line Loading @@ -3090,7 +3090,7 @@ TEST_F(ResolverTest, GetAddrInfo_Dns64Synthesize) { // Expect that there are two queries, one AAAA (which returns no records) and one A // (which returns 1.2.3.4). EXPECT_EQ(2U, GetNumQueries(dns, host_name)); EXPECT_EQ(ToString(result), "64:ff9b::102:304"); EXPECT_THAT(ToStrings(result), testing::ElementsAre("64:ff9b::102:304", "1.2.3.4")); // Stopping NAT64 prefix discovery disables synthesis. EXPECT_TRUE(mDnsClient.resolvService()->stopPrefix64Discovery(TEST_NETID).isOk()); Loading Loading @@ -3166,7 +3166,8 @@ TEST_F(ResolverTest, GetAddrInfo_Dns64SynthesizeMultiAnswers) { // Synthesize AAAA if there's no AAAA answer and AF_UNSPEC is specified. EXPECT_THAT(ToStrings(result), testing::ElementsAre("64:ff9b::102:304", "64:ff9b::808:808", "64:ff9b::5175:15ca")); testing::ElementsAre("64:ff9b::102:304", "64:ff9b::808:808", "64:ff9b::5175:15ca", "1.2.3.4", "8.8.8.8", "81.117.21.202")); } TEST_F(ResolverTest, GetAddrInfo_Dns64Canonname) { Loading Loading @@ -3199,8 +3200,8 @@ TEST_F(ResolverTest, GetAddrInfo_Dns64Canonname) { return fmt::format("family={}, flags={}", family, flags); } } testConfigs[]{ {AF_UNSPEC, 0, {"64:ff9b::102:304"}, nullptr}, {AF_UNSPEC, AI_CANONNAME, {"64:ff9b::102:304"}, "v4only.example.com"}, {AF_UNSPEC, 0, {"64:ff9b::102:304", "1.2.3.4"}, nullptr}, {AF_UNSPEC, AI_CANONNAME, {"64:ff9b::102:304", "1.2.3.4"}, "v4only.example.com"}, {AF_INET6, 0, {"64:ff9b::102:304"} , nullptr}, {AF_INET6, AI_CANONNAME, {"64:ff9b::102:304"} , "v4only.example.com"}, }; Loading @@ -3213,12 +3214,12 @@ TEST_F(ResolverTest, GetAddrInfo_Dns64Canonname) { .ai_family = config.family, .ai_flags = config.flags, .ai_socktype = SOCK_DGRAM}; ScopedAddrinfo result = safe_getaddrinfo("v4only", nullptr, &hints); ASSERT_TRUE(result != nullptr); EXPECT_EQ(ToString(result), "64:ff9b::102:304"); const auto* ai = result.get(); ASSERT_TRUE(ai != nullptr); EXPECT_THAT(ToStrings(result), testing::ElementsAreArray(config.expectedAddresses)); for (const auto* ai = result.get(); ai != nullptr; ai = ai->ai_next) { EXPECT_STREQ(ai->ai_canonname, config.expectedCanonname); } } } TEST_F(ResolverTest, GetAddrInfo_Dns64QuerySpecified) { constexpr char listen_addr[] = "::1"; Loading Loading @@ -3308,7 +3309,7 @@ TEST_F(ResolverTest, GetAddrInfo_Dns64QueryUnspecifiedNoV6) { EXPECT_EQ(2U, GetNumQueries(dns, host_name)); // Synthesize AAAA if there's no AAAA answer and AF_UNSPEC is specified. EXPECT_EQ(ToString(result), "64:ff9b::102:304"); EXPECT_THAT(ToStrings(result), testing::ElementsAre("64:ff9b::102:304", "1.2.3.4")); } TEST_F(ResolverTest, GetAddrInfo_Dns64QuerySpecialUseIPv4Addresses) { Loading Loading @@ -3407,7 +3408,8 @@ TEST_F(ResolverTest, GetAddrInfo_Dns64QueryWithNullArgumentHints) { ScopedAddrinfo result = safe_getaddrinfo("v4only", nullptr, nullptr); EXPECT_TRUE(result != nullptr); EXPECT_LE(2U, GetNumQueries(dns, host_name)); EXPECT_EQ(ToString(result), "64:ff9b::102:304"); EXPECT_THAT(ToStrings(result), testing::ElementsAre("64:ff9b::102:304", "64:ff9b::102:304", "1.2.3.4", "1.2.3.4")); dns.clearQueries(); // Do not synthesize AAAA if there's at least one AAAA answer. Loading Loading
DnsProxyListener.cpp +108 −19 Original line number Diff line number Diff line Loading @@ -469,7 +469,7 @@ void logDnsQueryResult(const addrinfo* res) { NI_NUMERICHOST); if (!ret) { LOG(DEBUG) << __func__ << ": [" << i << "] " << ai->ai_flags << " " << ai->ai_family << " " << ai->ai_socktype << " " << ai->ai_protocol; << " " << ai->ai_socktype << " " << ai->ai_protocol << " " << ip_addr; } else { LOG(DEBUG) << __func__ << ": [" << i << "] numeric hostname translation fail " << ret; } Loading Loading @@ -523,30 +523,95 @@ bool synthesizeNat64PrefixWithARecord(const netdutils::IPPrefix& prefix, struct return true; } bool synthesizeNat64PrefixWithARecord(const netdutils::IPPrefix& prefix, addrinfo* result) { if (result == nullptr) return false; if (!onlyNonSpecialUseIPv4Addresses(result)) return false; bool synthesizeNat64PrefixWithARecord(const netdutils::IPPrefix& prefix, addrinfo** res, bool unspecWantedButNoIPv6) { if (*res == nullptr) return false; if (!onlyNonSpecialUseIPv4Addresses(*res)) return false; if (!isValidNat64Prefix(prefix)) return false; struct sockaddr_storage ss = netdutils::IPSockAddr(prefix.ip()); struct sockaddr_in6* v6prefix = (struct sockaddr_in6*)&ss; for (addrinfo* ai = result; ai; ai = ai->ai_next) { struct sockaddr_in sinOriginal = *(struct sockaddr_in*)ai->ai_addr; struct sockaddr_in6* sin6 = (struct sockaddr_in6*)ai->ai_addr; memset(sin6, 0, sizeof(sockaddr_in6)); const sockaddr_storage ss = netdutils::IPSockAddr(prefix.ip()); const sockaddr_in6* v6prefix = (sockaddr_in6*)&ss; addrinfo* const head4 = *res; addrinfo* head6 = nullptr; addrinfo* cur6 = nullptr; // Build a synthesized AAAA addrinfo list from the queried A addrinfo list. Here is the diagram // for the relationship of pointers. // // head4: point to the first queried A addrinfo // | // v // +-------------+ +-------------+ // | addrinfo4#1 |-->| addrinfo4#2 |--> .. queried A addrinfo(s) for DNS64 synthesis // +-------------+ +-------------+ // ^ // | // cur4: current worked-on queried A addrinfo // // head6: point to the first synthesized AAAA addrinfo // | // v // +-------------+ +-------------+ // | addrinfo6#1 |-->| addrinfo6#2 |--> .. synthesized DNS64 AAAA addrinfo(s) // +-------------+ +-------------+ // ^ // | // cur6: current worked-on synthesized addrinfo // for (const addrinfo* cur4 = head4; cur4; cur4 = cur4->ai_next) { // Allocate a space for a synthesized AAAA addrinfo. Note that the addrinfo and sockaddr // occupy one contiguous block of memory and are allocated and freed as a single block. // See get_ai and freeaddrinfo in packages/modules/DnsResolver/getaddrinfo.cpp. addrinfo* sa = (addrinfo*)calloc(1, sizeof(addrinfo) + sizeof(sockaddr_in6)); if (sa == nullptr) { LOG(ERROR) << "allocate memory failed for synthesized result"; freeaddrinfo(head6); return false; } // Initialize the synthesized AAAA addrinfo by the queried A addrinfo. The ai_addr will be // set lately. sa->ai_flags = cur4->ai_flags; sa->ai_family = AF_INET6; sa->ai_socktype = cur4->ai_socktype; sa->ai_protocol = cur4->ai_protocol; sa->ai_addrlen = sizeof(sockaddr_in6); sa->ai_addr = (sockaddr*)(sa + 1); sa->ai_canonname = nullptr; sa->ai_next = nullptr; if (cur4->ai_canonname != nullptr) { sa->ai_canonname = strdup(cur4->ai_canonname); if (sa->ai_canonname == nullptr) { LOG(ERROR) << "allocate memory failed for canonname"; freeaddrinfo(sa); freeaddrinfo(head6); return false; } } // Synthesize /96 NAT64 prefix in place. The space has reserved by get_ai() in // system/netd/resolv/getaddrinfo.cpp. // Synthesize /96 NAT64 prefix with the queried IPv4 address. const sockaddr_in* sin4 = (sockaddr_in*)cur4->ai_addr; sockaddr_in6* sin6 = (sockaddr_in6*)sa->ai_addr; sin6->sin6_addr = v6prefix->sin6_addr; sin6->sin6_addr.s6_addr32[3] = sinOriginal.sin_addr.s_addr; sin6->sin6_addr.s6_addr32[3] = sin4->sin_addr.s_addr; sin6->sin6_family = AF_INET6; sin6->sin6_port = sinOriginal.sin_port; ai->ai_addrlen = sizeof(struct sockaddr_in6); ai->ai_family = AF_INET6; sin6->sin6_port = sin4->sin_port; // If the synthesized list is empty, this becomes the first element. if (head6 == nullptr) { head6 = sa; } // Add this element to the end of the synthesized list. if (cur6 != nullptr) { cur6->ai_next = sa; } cur6 = sa; if (WOULD_LOG(VERBOSE)) { char buf[INET6_ADDRSTRLEN]; // big enough for either IPv4 or IPv6 inet_ntop(AF_INET, &sinOriginal.sin_addr.s_addr, buf, sizeof(buf)); inet_ntop(AF_INET, &sin4->sin_addr.s_addr, buf, sizeof(buf)); LOG(VERBOSE) << __func__ << ": DNS A record: " << buf; inet_ntop(AF_INET6, &v6prefix->sin6_addr, buf, sizeof(buf)); LOG(VERBOSE) << __func__ << ": NAT64 prefix: " << buf; Loading @@ -554,7 +619,31 @@ bool synthesizeNat64PrefixWithARecord(const netdutils::IPPrefix& prefix, addrinf LOG(VERBOSE) << __func__ << ": DNS64 Synthesized AAAA record: " << buf; } } logDnsQueryResult(result); // Simply concatenate the synthesized AAAA addrinfo list and the queried A addrinfo list when // AF_UNSPEC is specified. Prefer that synthesized IPv6 addresses have higher precedence than // IPv4 addresses because having a NAT64 prefix implies that there is an IPv6-only network. // Note that head6 and cur6 should be non-null because there was at least one IPv4 address // synthesized. // TODO: Sort the concatenated addresses by RFC 6724 section 2.1. Note that the // socket type and protocol may be considered as well. Currently, resolv_getaddrinfo() calls // explore_fqdn() times by the different items of explore_options. It means that // _rfc6724_sort() only sorts the results in each explore_options and concatenates each results // into one. For example, getaddrinfo() is called with null hints for a domain name which has // both IPv4 and IPv6 addresses. The address order of the result addrinfo may be: // 2001:db8::102:304 (socktype=2, protocol=17) -> 1.2.3.4 (socktype=2, protocol=17) -> // 2001:db8::102:304 (socktype=1, protocol=6) -> 1.2.3.4 (socktype=1, protocol=6) // In above example, the first two results come from one explore option and the last two come // from another one. They are sorted first, and then concatenate together to be the result. // See also resolv_getaddrinfo in packages/modules/DnsResolver/getaddrinfo.cpp. if (unspecWantedButNoIPv6) { cur6->ai_next = head4; } else { freeaddrinfo(head4); } *res = head6; logDnsQueryResult(*res); return true; } Loading Loading @@ -713,7 +802,7 @@ void DnsProxyListener::GetAddrInfoHandler::doDns64Synthesis(int32_t* rv, addrinf } } if (!synthesizeNat64PrefixWithARecord(prefix, *res)) { if (!synthesizeNat64PrefixWithARecord(prefix, res, unspecWantedButNoIPv6)) { if (ipv6WantedButNoData) { // If caller wants IPv6 answers but no data and failed to synthesize IPv6 answers, // don't return the IPv4 answers. Loading
tests/resolv_integration_test.cpp +14 −12 Original line number Diff line number Diff line Loading @@ -3090,7 +3090,7 @@ TEST_F(ResolverTest, GetAddrInfo_Dns64Synthesize) { // Expect that there are two queries, one AAAA (which returns no records) and one A // (which returns 1.2.3.4). EXPECT_EQ(2U, GetNumQueries(dns, host_name)); EXPECT_EQ(ToString(result), "64:ff9b::102:304"); EXPECT_THAT(ToStrings(result), testing::ElementsAre("64:ff9b::102:304", "1.2.3.4")); // Stopping NAT64 prefix discovery disables synthesis. EXPECT_TRUE(mDnsClient.resolvService()->stopPrefix64Discovery(TEST_NETID).isOk()); Loading Loading @@ -3166,7 +3166,8 @@ TEST_F(ResolverTest, GetAddrInfo_Dns64SynthesizeMultiAnswers) { // Synthesize AAAA if there's no AAAA answer and AF_UNSPEC is specified. EXPECT_THAT(ToStrings(result), testing::ElementsAre("64:ff9b::102:304", "64:ff9b::808:808", "64:ff9b::5175:15ca")); testing::ElementsAre("64:ff9b::102:304", "64:ff9b::808:808", "64:ff9b::5175:15ca", "1.2.3.4", "8.8.8.8", "81.117.21.202")); } TEST_F(ResolverTest, GetAddrInfo_Dns64Canonname) { Loading Loading @@ -3199,8 +3200,8 @@ TEST_F(ResolverTest, GetAddrInfo_Dns64Canonname) { return fmt::format("family={}, flags={}", family, flags); } } testConfigs[]{ {AF_UNSPEC, 0, {"64:ff9b::102:304"}, nullptr}, {AF_UNSPEC, AI_CANONNAME, {"64:ff9b::102:304"}, "v4only.example.com"}, {AF_UNSPEC, 0, {"64:ff9b::102:304", "1.2.3.4"}, nullptr}, {AF_UNSPEC, AI_CANONNAME, {"64:ff9b::102:304", "1.2.3.4"}, "v4only.example.com"}, {AF_INET6, 0, {"64:ff9b::102:304"} , nullptr}, {AF_INET6, AI_CANONNAME, {"64:ff9b::102:304"} , "v4only.example.com"}, }; Loading @@ -3213,12 +3214,12 @@ TEST_F(ResolverTest, GetAddrInfo_Dns64Canonname) { .ai_family = config.family, .ai_flags = config.flags, .ai_socktype = SOCK_DGRAM}; ScopedAddrinfo result = safe_getaddrinfo("v4only", nullptr, &hints); ASSERT_TRUE(result != nullptr); EXPECT_EQ(ToString(result), "64:ff9b::102:304"); const auto* ai = result.get(); ASSERT_TRUE(ai != nullptr); EXPECT_THAT(ToStrings(result), testing::ElementsAreArray(config.expectedAddresses)); for (const auto* ai = result.get(); ai != nullptr; ai = ai->ai_next) { EXPECT_STREQ(ai->ai_canonname, config.expectedCanonname); } } } TEST_F(ResolverTest, GetAddrInfo_Dns64QuerySpecified) { constexpr char listen_addr[] = "::1"; Loading Loading @@ -3308,7 +3309,7 @@ TEST_F(ResolverTest, GetAddrInfo_Dns64QueryUnspecifiedNoV6) { EXPECT_EQ(2U, GetNumQueries(dns, host_name)); // Synthesize AAAA if there's no AAAA answer and AF_UNSPEC is specified. EXPECT_EQ(ToString(result), "64:ff9b::102:304"); EXPECT_THAT(ToStrings(result), testing::ElementsAre("64:ff9b::102:304", "1.2.3.4")); } TEST_F(ResolverTest, GetAddrInfo_Dns64QuerySpecialUseIPv4Addresses) { Loading Loading @@ -3407,7 +3408,8 @@ TEST_F(ResolverTest, GetAddrInfo_Dns64QueryWithNullArgumentHints) { ScopedAddrinfo result = safe_getaddrinfo("v4only", nullptr, nullptr); EXPECT_TRUE(result != nullptr); EXPECT_LE(2U, GetNumQueries(dns, host_name)); EXPECT_EQ(ToString(result), "64:ff9b::102:304"); EXPECT_THAT(ToStrings(result), testing::ElementsAre("64:ff9b::102:304", "64:ff9b::102:304", "1.2.3.4", "1.2.3.4")); dns.clearQueries(); // Do not synthesize AAAA if there's at least one AAAA answer. Loading