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

Commit a11d8675 authored by Ken Chen's avatar Ken Chen
Browse files

Add .local resolution fallback mechanism

RFC6762 specifies that the ".local" suffix is specific to Multicast DNS.
It recommends against using ".local" as a Unicast DNS top-level domain.
However, some network environments still setting up ".local" as Unicast
DNS top-level domain. To better support them, DNS resolver provides a
fallback mechanism to send Unicast DNS queries when Multicast DNS is
failed.

Bug: 221359963
Test: atest
Change-Id: I1b8b20333481c40faf6f9fcbf9884a7545c87b2b
parent daa67f1b
Loading
Loading
Loading
Loading
+7 −9
Original line number Diff line number Diff line
@@ -483,11 +483,8 @@ int res_nsend(ResState* statp, span<const uint8_t> msg, span<uint8_t> ans, int*
        mDnsQueryEvent->set_linux_errno(static_cast<LinuxErrno>(terrno));
        resolv_stats_add(statp->netid, receivedMdnsAddr, mDnsQueryEvent);

        if (resplen <= 0) {
            _resolv_cache_query_failed(statp->netid, msg, flags);
            return -terrno;
        }
        LOG(DEBUG) << __func__ << ": got answer:";
        if (resplen > 0) {
            LOG(DEBUG) << __func__ << ": got answer from mDNS:";
            res_pquery(ans.first(resplen));

            if (cache_status == RESOLV_CACHE_NOTFOUND) {
@@ -495,6 +492,7 @@ int res_nsend(ResState* statp, span<const uint8_t> msg, span<uint8_t> ans, int*
            }
            return resplen;
        }
    }

    if (statp->nameserverCount() == 0) {
        // We have no nameservers configured and it's not a MDNS resolution, so there's no
+59 −0
Original line number Diff line number Diff line
@@ -6519,6 +6519,65 @@ TEST_F(ResolverTest, MdnsGetAddrInfo_cnamesIllegalRdata) {
    EXPECT_TRUE(result == nullptr);
}

// Test if .local resolution will try unicast when multicast is failed.
TEST_F(ResolverTest, MdnsGetAddrInfo_fallback) {
    constexpr char v6addr[] = "::1.2.3.4";
    constexpr char v4addr[] = "1.2.3.4";
    constexpr char host_name[] = "hello.local.";
    test::DNSResponder mdnsv4("127.0.0.3", test::kDefaultMdnsListenService,
                              static_cast<ns_rcode>(-1));
    test::DNSResponder mdnsv6("::1", test::kDefaultMdnsListenService, static_cast<ns_rcode>(-1));
    // Set unresponsive on multicast.
    mdnsv4.setResponseProbability(0.0);
    mdnsv6.setResponseProbability(0.0);
    ASSERT_TRUE(mdnsv4.startServer());
    ASSERT_TRUE(mdnsv6.startServer());

    const std::vector<DnsRecord> records = {
            {host_name, ns_type::ns_t_a, v4addr},
            {host_name, ns_type::ns_t_aaaa, v6addr},
    };
    test::DNSResponder dns("127.0.0.3");
    StartDns(dns, records);
    ASSERT_TRUE(mDnsClient.SetResolversForNetwork());

    static const struct TestConfig {
        int ai_family;
        const std::vector<std::string> expected_addr;
    } testConfigs[]{
            {AF_INET, {v4addr}},
            {AF_INET6, {v6addr}},
            {AF_UNSPEC, {v4addr, v6addr}},
    };

    for (const auto& config : testConfigs) {
        SCOPED_TRACE(fmt::format("family: {}", config.ai_family));
        addrinfo hints = {.ai_family = config.ai_family, .ai_socktype = SOCK_DGRAM};
        ScopedAddrinfo result = safe_getaddrinfo("hello.local", nullptr, &hints);
        EXPECT_TRUE(result != nullptr);
        if (config.ai_family == AF_INET) {
            EXPECT_EQ(1U, GetNumQueries(mdnsv4, host_name));
            EXPECT_EQ(0U, GetNumQueries(mdnsv6, host_name));
            EXPECT_EQ(1U, GetNumQueries(dns, host_name));
        } else if (config.ai_family == AF_INET6) {
            EXPECT_EQ(0U, GetNumQueries(mdnsv4, host_name));
            EXPECT_EQ(1U, GetNumQueries(mdnsv6, host_name));
            EXPECT_EQ(1U, GetNumQueries(dns, host_name));
        } else {
            EXPECT_EQ(1U, GetNumQueries(mdnsv4, host_name));
            EXPECT_EQ(1U, GetNumQueries(mdnsv6, host_name));
            EXPECT_EQ(2U, GetNumQueries(dns, host_name));
        }
        std::string result_str = ToString(result);
        EXPECT_THAT(ToStrings(result), testing::UnorderedElementsAreArray(config.expected_addr));

        mdnsv4.clearQueries();
        mdnsv6.clearQueries();
        dns.clearQueries();
        ASSERT_TRUE(mDnsClient.resolvService()->flushNetworkCache(TEST_NETID).isOk());
    }
}

// ResolverMultinetworkTest is used to verify multinetwork functionality. Here's how it works:
// The resolver sends queries to address A, and then there will be a TunForwarder helping forward
// the packets to address B, which is the address on which the testing server is listening. The
+77 −2
Original line number Diff line number Diff line
@@ -307,9 +307,9 @@ class BasePrivateDnsTest : public BaseTest {
        BaseTest::TearDown();
    }

    void sendQueryAndCheckResult() {
    void sendQueryAndCheckResult(const char* host_name = kQueryHostname) {
        const addrinfo hints = {.ai_socktype = SOCK_DGRAM};
        ScopedAddrinfo result = safe_getaddrinfo(kQueryHostname, nullptr, &hints);
        ScopedAddrinfo result = safe_getaddrinfo(host_name, nullptr, &hints);
        EXPECT_THAT(ToStrings(result),
                    testing::ElementsAreArray({kQueryAnswerAAAA, kQueryAnswerA}));
    };
@@ -380,6 +380,12 @@ class TransportParameterizedTest : public BasePrivateDnsTest,
            ASSERT_TRUE(doh_backend.startServer());
            ASSERT_TRUE(doh.startServer());
        }
        SetMdnsRoute();
    }

    void TearDown() override {
        RemoveMdnsRoute();
        BasePrivateDnsTest::TearDown();
    }

    bool testParamHasDot() { return GetParam() & kDotBit; }
@@ -437,6 +443,75 @@ TEST_P(TransportParameterizedTest, GetAddrInfo) {
    }
}

TEST_P(TransportParameterizedTest, MdnsGetAddrInfo_fallback) {
    constexpr char host_name[] = "hello.local.";
    test::DNSResponder mdnsv4("127.0.0.3", test::kDefaultMdnsListenService,
                              static_cast<ns_rcode>(-1));
    test::DNSResponder mdnsv6("::1", test::kDefaultMdnsListenService, static_cast<ns_rcode>(-1));
    // Set unresponsive on multicast.
    mdnsv4.setResponseProbability(0.0);
    mdnsv6.setResponseProbability(0.0);
    ASSERT_TRUE(mdnsv4.startServer());
    ASSERT_TRUE(mdnsv6.startServer());

    const std::vector<DnsRecord> records = {
            {host_name, ns_type::ns_t_a, kQueryAnswerA},
            {host_name, ns_type::ns_t_aaaa, kQueryAnswerAAAA},
    };

    for (const auto& r : records) {
        dns.addMapping(r.host_name, r.type, r.addr);
        dot_backend.addMapping(r.host_name, r.type, r.addr);
        doh_backend.addMapping(r.host_name, r.type, r.addr);
    }

    auto parcel = DnsResponderClient::GetDefaultResolverParamsParcel();
    ASSERT_TRUE(mDnsClient.SetResolversFromParcel(parcel));

    if (testParamHasDoh()) EXPECT_TRUE(WaitForDohValidation(test::kDefaultListenAddr, true));
    if (testParamHasDot()) EXPECT_TRUE(WaitForDotValidation(test::kDefaultListenAddr, true));

    // This waiting time is expected to avoid that the DoH validation event interferes other tests.
    if (!testParamHasDoh()) waitForDohValidationFailed();

    // Have the test independent of the number of sent queries in private DNS validation, because
    // the DnsResolver can send either 1 or 2 queries in DoT validation.
    if (testParamHasDoh()) {
        doh.clearQueries();
    }
    if (testParamHasDot()) {
        EXPECT_TRUE(dot.waitForQueries(1));
        dot.clearQueries();
    }
    dns.clearQueries();

    EXPECT_NO_FAILURE(sendQueryAndCheckResult("hello.local"));
    EXPECT_EQ(1U, GetNumQueries(mdnsv4, host_name));
    EXPECT_EQ(1U, GetNumQueries(mdnsv6, host_name));
    if (testParamHasDoh()) {
        EXPECT_NO_FAILURE(expectQueries(0 /* dns */, 0 /* dot */, 2 /* doh */));
    } else {
        EXPECT_NO_FAILURE(expectQueries(0 /* dns */, 2 /* dot */, 0 /* doh */));
    }

    // Stop the private DNS servers. Since we are in opportunistic mode, queries will
    // fall back to the cleartext nameserver.
    flushCache();
    dot.stopServer();
    doh.stopServer();
    mdnsv4.clearQueries();
    mdnsv6.clearQueries();

    EXPECT_NO_FAILURE(sendQueryAndCheckResult("hello.local"));
    EXPECT_EQ(1U, GetNumQueries(mdnsv4, host_name));
    EXPECT_EQ(1U, GetNumQueries(mdnsv6, host_name));
    if (testParamHasDoh()) {
        EXPECT_NO_FAILURE(expectQueries(2 /* dns */, 0 /* dot */, 2 /* doh */));
    } else {
        EXPECT_NO_FAILURE(expectQueries(2 /* dns */, 2 /* dot */, 0 /* doh */));
    }
}

class PrivateDnsDohTest : public BasePrivateDnsTest {
  protected:
    void SetUp() override {
+6 −0
Original line number Diff line number Diff line
@@ -1037,6 +1037,9 @@ TEST_F(ResolvGetAddrInfoTest, MdnsResponderTimeout) {
    ASSERT_TRUE(mdnsv4.startServer());
    ASSERT_TRUE(mdnsv6.startServer());
    ASSERT_EQ(0, SetResolvers());
    test::DNSResponder dns("127.0.0.3", test::kDefaultListenService, static_cast<ns_rcode>(-1));
    dns.setResponseProbability(0.0);
    ASSERT_TRUE(dns.startServer());

    for (const auto& family : {AF_INET, AF_INET6, AF_UNSPEC}) {
        SCOPED_TRACE(fmt::format("family: {}, host_name: {}", family, host_name));
@@ -1825,6 +1828,9 @@ TEST_F(GetHostByNameForNetContextTest, MdnsResponderTimeout) {
    ASSERT_TRUE(mdnsv4.startServer());
    ASSERT_TRUE(mdnsv6.startServer());
    ASSERT_EQ(0, SetResolvers());
    test::DNSResponder dns("127.0.0.3", test::kDefaultListenService, static_cast<ns_rcode>(-1));
    dns.setResponseProbability(0.0);
    ASSERT_TRUE(dns.startServer());

    for (const auto& family : {AF_INET, AF_INET6}) {
        SCOPED_TRACE(fmt::format("family: {}, host_name: {}", family, host_name));