Support alternative handling on truncated DNS response

The default behavior of UDP truncated DNS response is that the DNS
resolver fallback on TCP on the same server. If the fallback retry is
failed, do TCP retry on the rest of servers. However, OEM reported
that the rest of servers may respond a valid answer on UDP connection,
but support no TCP connection. Thus, an alternative truncated response
handling mode is added for OEMs. With the alternative mode, the DNS
resolver fallback retries on TCP on the same server. If the TCP retry
is failed, do UDP retry on the next server, then TCP, and so on.

Set mode by ResolverExperimentalOptionsParcel in ResolverParamsParcel.
tcMode=0 or absent, run default behavior (TCP-only on each DNS server).
tcMode=1, run alternative mode (UDP first on each DNS server).
other values are invalid input, take no effect.

Bug: 139646101
Change-Id: I724cc54bd9fad95954de84c281dd6f1d0b764caa
diff --git a/tests/resolv_integration_test.cpp b/tests/resolv_integration_test.cpp
index cc033ac..dd0760a 100644
--- a/tests/resolv_integration_test.cpp
+++ b/tests/resolv_integration_test.cpp
@@ -156,12 +156,6 @@
     static void TearDownTestCase() { AIBinder_DeathRecipient_delete(sResolvDeathRecipient); }
 
   protected:
-    struct DnsRecord {
-        std::string host_name;  // host name
-        ns_type type;           // record type
-        std::string addr;       // ipv4/v6 address
-    };
-
     void SetUp() { mDnsClient.SetUp(); }
     void TearDown() { mDnsClient.TearDown(); }
 
@@ -1010,7 +1004,7 @@
     test::DNSResponder dns;
     StartDns(dns, {});
     auto resolverParams = DnsResponderClient::GetDefaultResolverParamsParcel();
-    resolverParams.hosts = invalidCustHosts;
+    resolverParams.experimentalOptions.hosts = invalidCustHosts;
     ASSERT_TRUE(mDnsClient.resolvService()->setResolverConfiguration(resolverParams).isOk());
     for (const auto& hostname : {hostnameNoip, hostnameInvalidip}) {
         // The query won't get data from customized table because of invalid customized table
@@ -1085,7 +1079,7 @@
         StartDns(dns, config.dnsserverHosts);
 
         auto resolverParams = DnsResponderClient::GetDefaultResolverParamsParcel();
-        resolverParams.hosts = config.customizedHosts;
+        resolverParams.experimentalOptions.hosts = config.customizedHosts;
         ASSERT_TRUE(mDnsClient.resolvService()->setResolverConfiguration(resolverParams).isOk());
         const addrinfo hints = {.ai_family = AF_UNSPEC, .ai_socktype = SOCK_STREAM};
         ScopedAddrinfo result = safe_getaddrinfo(config.name.c_str(), nullptr, &hints);
@@ -1121,7 +1115,7 @@
     StartDns(dns, dnsSvHostV4V6);
     auto resolverParams = DnsResponderClient::GetDefaultResolverParamsParcel();
 
-    resolverParams.hosts = custHostV4V6;
+    resolverParams.experimentalOptions.hosts = custHostV4V6;
     ASSERT_TRUE(mDnsClient.resolvService()->setResolverConfiguration(resolverParams).isOk());
     const addrinfo hints = {.ai_family = AF_UNSPEC, .ai_socktype = SOCK_STREAM};
     ScopedAddrinfo result = safe_getaddrinfo(hostnameV4V6, nullptr, &hints);
@@ -1129,7 +1123,7 @@
     EXPECT_THAT(ToStrings(result), testing::UnorderedElementsAreArray({custAddrV4, custAddrV6}));
     EXPECT_EQ(0U, GetNumQueries(dns, hostnameV4V6));
 
-    resolverParams.hosts = {};
+    resolverParams.experimentalOptions.hosts = {};
     ASSERT_TRUE(mDnsClient.resolvService()->setResolverConfiguration(resolverParams).isOk());
     result = safe_getaddrinfo(hostnameV4V6, nullptr, &hints);
     ASSERT_TRUE(result != nullptr);
@@ -3892,6 +3886,62 @@
     EXPECT_EQ(0U, GetNumQueriesForProtocol(dns, IPPROTO_UDP, kHelloExampleCom));
 }
 
+TEST_F(ResolverTest, TruncatedRspMode) {
+    constexpr char listen_addr[] = "127.0.0.4";
+    constexpr char listen_addr2[] = "127.0.0.5";
+    constexpr char listen_srv[] = "53";
+
+    test::DNSResponder dns(listen_addr, listen_srv, static_cast<ns_rcode>(-1));
+    test::DNSResponder dns2(listen_addr2, listen_srv, static_cast<ns_rcode>(-1));
+    // dns supports UDP only, dns2 support UDP and TCP
+    dns.setResponseProbability(0.0, IPPROTO_TCP);
+    StartDns(dns, kLargeCnameChainRecords);
+    StartDns(dns2, kLargeCnameChainRecords);
+
+    const struct TestConfig {
+        const std::optional<int32_t> tcMode;
+        const bool ret;
+        const unsigned numQueries;
+        std::string asParameters() const {
+            return StringPrintf("tcMode: %d, ret: %s, numQueries: %u", tcMode.value_or(-1),
+                                ret ? "true" : "false", numQueries);
+        }
+    } testConfigs[]{
+            // clang-format off
+            {std::nullopt,                                      true,  0}, /* mode unset */
+            {aidl::android::net::IDnsResolver::TC_MODE_DEFAULT, true,  0}, /* default mode */
+            {aidl::android::net::IDnsResolver::TC_MODE_UDP_TCP, true,  1}, /* alternative mode */
+            {-666,                                              false, 1}, /* invalid input */
+            // clang-format on
+    };
+
+    for (const auto& config : testConfigs) {
+        SCOPED_TRACE(config.asParameters());
+
+        ResolverParamsParcel parcel = DnsResponderClient::GetDefaultResolverParamsParcel();
+        parcel.servers = {listen_addr, listen_addr2};
+        if (config.tcMode) {
+            parcel.experimentalOptions.tcMode = config.tcMode.value();
+        }
+        ASSERT_EQ(mDnsClient.resolvService()->setResolverConfiguration(parcel).isOk(), config.ret);
+
+        const addrinfo hints = {.ai_family = AF_INET, .ai_socktype = SOCK_DGRAM};
+        ScopedAddrinfo result = safe_getaddrinfo("hello", nullptr, &hints);
+        ASSERT_TRUE(result != nullptr);
+        EXPECT_EQ(ToString(result), kHelloExampleComAddrV4);
+        // TC_MODE_DEFAULT: resolver retries on TCP-only on each name server.
+        // TC_MODE_UDP_TCP: resolver retries on TCP on the same server, falls back to UDP from next.
+        ASSERT_EQ(GetNumQueriesForProtocol(dns, IPPROTO_UDP, kHelloExampleCom), 1U);
+        ASSERT_EQ(GetNumQueriesForProtocol(dns, IPPROTO_TCP, kHelloExampleCom), 1U);
+        ASSERT_EQ(GetNumQueriesForProtocol(dns2, IPPROTO_UDP, kHelloExampleCom), config.numQueries);
+        ASSERT_EQ(GetNumQueriesForProtocol(dns2, IPPROTO_TCP, kHelloExampleCom), 1U);
+
+        dns.clearQueries();
+        dns2.clearQueries();
+        ASSERT_TRUE(mDnsClient.resolvService()->flushNetworkCache(TEST_NETID).isOk());
+    }
+}
+
 // Parameterized tests.
 // TODO: Merge the existing tests as parameterized test if possible.
 // TODO: Perhaps move parameterized tests to an independent file.
@@ -4134,35 +4184,8 @@
 TEST_P(ResolverParameterizedTest, TruncatedResponse) {
     const auto& calltype = GetParam();
 
-    const size_t kMaxmiumLabelSize = 63;  // see RFC 1035 section 2.3.4.
-    const std::string kDomainName = ".example.com.";
-    const std::string kCnameA = std::string(kMaxmiumLabelSize, 'a') + kDomainName;
-    const std::string kCnameB = std::string(kMaxmiumLabelSize, 'b') + kDomainName;
-    const std::string kCnameC = std::string(kMaxmiumLabelSize, 'c') + kDomainName;
-    const std::string kCnameD = std::string(kMaxmiumLabelSize, 'd') + kDomainName;
-
-    // Build a response message which exceeds 512 bytes by CNAME chain.
-    //
-    // Ignoring the other fields of the message, the response message has 8 CNAMEs in 5 answer RRs
-    // and each CNAME has 77 bytes as the follows. The response message at least has 616 bytes in
-    // answer section and has already exceeded 512 bytes totally.
-    //
-    // The CNAME is presented as:
-    //   0   1            64  65                          72  73          76  77
-    //   +---+--........--+---+---+---+---+---+---+---+---+---+---+---+---+---+
-    //   | 63| {x, .., x} | 7 | e | x | a | m | p | l | e | 3 | c | o | m | 0 |
-    //   +---+--........--+---+---+---+---+---+---+---+---+---+---+---+---+---+
-    //          ^-- x = {a, b, c, d}
-    //
-    const std::vector<DnsRecord> records = {
-            {kHelloExampleCom, ns_type::ns_t_cname, kCnameA},
-            {kCnameA, ns_type::ns_t_cname, kCnameB},
-            {kCnameB, ns_type::ns_t_cname, kCnameC},
-            {kCnameC, ns_type::ns_t_cname, kCnameD},
-            {kCnameD, ns_type::ns_t_a, kHelloExampleComAddrV4},
-    };
     test::DNSResponder dns;
-    StartDns(dns, records);
+    StartDns(dns, kLargeCnameChainRecords);
     ASSERT_TRUE(mDnsClient.SetResolversForNetwork());
 
     // Expect UDP response is truncated. The resolver retries over TCP. See RFC 1035 section 4.2.1.