Skip to content

Commit

Permalink
dns: add round-robin nameserver rotation option to c-ares resolver (#…
Browse files Browse the repository at this point in the history
…37108)

This PR adds support for configuring round-robin nameserver
selection in the c-ares DNS resolver.
When enabled, this will rotate through the configured nameservers for
each resolution request, helping to distribute query load across
multiple nameservers.

The feature allows better load distribution across multiple nameservers
without requiring any changes to DNS server configuration.

Signed-off-by: Rohit Agrawal <[email protected]>
  • Loading branch information
agrawroh authored Nov 12, 2024
1 parent 3a1a72e commit b5c15a3
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE;
// [#extension: envoy.network.dns_resolver.cares]

// Configuration for c-ares DNS resolver.
// [#next-free-field: 8]
// [#next-free-field: 9]
message CaresDnsResolverConfig {
// A list of dns resolver addresses.
// :ref:`use_resolvers_as_fallback<envoy_v3_api_field_extensions.network.dns_resolver.cares.v3.CaresDnsResolverConfig.use_resolvers_as_fallback>`
Expand Down Expand Up @@ -61,4 +61,11 @@ message CaresDnsResolverConfig {
// Note: While the c-ares library defaults to 3 attempts, Envoy's default (if this field is unset) is 4 attempts.
// This adjustment was made to maintain the previous behavior after users reported an increase in DNS resolution times.
google.protobuf.UInt32Value query_tries = 7 [(validate.rules).uint32 = {gte: 1}];

// Enable round-robin selection of name servers for DNS resolution. When enabled, the resolver will cycle through the
// list of name servers for each resolution request. This can help distribute the query load across multiple name
// servers. If disabled (default), the resolver will try name servers in the order they are configured.
//
// Note: This setting overrides any system configuration for name server rotation.
bool rotate_nameservers = 8;
}
6 changes: 5 additions & 1 deletion changelogs/current.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,11 @@ new_features:
change: |
added :ref:`sourced_metadata <envoy_v3_api_field_config.rbac.v3.Permission.sourced_metadata>` which allows
specifying an optional source for the metadata to be matched in addition to the metadata matcher.
- area: c-ares
change: |
added nameserver rotation option to c-ares resolver. When enabled via :ref:rotate_nameservers
<envoy_v3_api_field_extensions.network.dns_resolver.cares.v3.CaresDnsResolverConfig.rotate_nameservers>, this
performs round-robin selection of the configured nameservers for each resolution to help distribute query load.
deprecated:
- area: rbac
change: |
Expand Down
8 changes: 7 additions & 1 deletion source/extensions/network/dns_resolver/cares/dns_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ DnsResolverImpl::DnsResolverImpl(
config, query_timeout_seconds, DEFAULT_QUERY_TIMEOUT_SECONDS))),
query_tries_(static_cast<uint32_t>(
PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, query_tries, DEFAULT_QUERY_TRIES))),
resolvers_csv_(resolvers_csv),
rotate_nameservers_(config.rotate_nameservers()), resolvers_csv_(resolvers_csv),
filter_unroutable_families_(config.filter_unroutable_families()),
scope_(root_scope.createScope("dns.cares.")), stats_(generateCaresDnsResolverStats(*scope_)) {
AresOptions options = defaultAresOptions();
Expand Down Expand Up @@ -119,6 +119,12 @@ DnsResolverImpl::AresOptions DnsResolverImpl::defaultAresOptions() {
options.optmask_ |= ARES_OPT_TRIES;
options.options_.tries = query_tries_;

if (rotate_nameservers_) {
options.optmask_ |= ARES_OPT_ROTATE;
} else {
options.optmask_ |= ARES_OPT_NOROTATE;
}

return options;
}

Expand Down
1 change: 1 addition & 0 deletions source/extensions/network/dns_resolver/cares/dns_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ class DnsResolverImpl : public DnsResolver, protected Logger::Loggable<Logger::I
const uint32_t udp_max_queries_;
const uint64_t query_timeout_seconds_;
const uint32_t query_tries_;
const bool rotate_nameservers_;
const absl::optional<std::string> resolvers_csv_;
const bool filter_unroutable_families_;
Stats::ScopeSharedPtr scope_;
Expand Down
46 changes: 46 additions & 0 deletions test/extensions/network/dns_resolver/cares/dns_impl_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -723,6 +723,7 @@ class DnsImplTest : public testing::TestWithParam<Address::IpVersion> {

cares.set_filter_unroutable_families(filterUnroutableFamilies());
cares.set_allocated_udp_max_queries(udpMaxQueries());
cares.set_rotate_nameservers(setRotateNameservers());

// Copy over the dns_resolver_options_.
cares.mutable_dns_resolver_options()->MergeFrom(dns_resolver_options);
Expand Down Expand Up @@ -962,6 +963,7 @@ class DnsImplTest : public testing::TestWithParam<Address::IpVersion> {
virtual void updateDnsResolverOptions(){};
virtual bool setResolverInConstructor() const { return false; }
virtual bool filterUnroutableFamilies() const { return false; }
virtual bool setRotateNameservers() const { return false; }
virtual ProtobufWkt::UInt32Value* udpMaxQueries() const { return 0; }
Stats::TestUtil::TestStore stats_store_;
NiceMock<Runtime::MockLoader> runtime_;
Expand Down Expand Up @@ -2243,5 +2245,49 @@ TEST_P(DnsImplAresFlagsForMaxUdpQueriesinTest, UdpMaxQueriesIsSet) {
ares_destroy_options(&opts);
}

class DnsImplAresFlagsForNameserverRotationTest : public DnsImplTest {
protected:
bool tcpOnly() const override { return false; }
bool setRotateNameservers() const override { return true; }
};

// Parameterize the DNS test server socket address.
INSTANTIATE_TEST_SUITE_P(IpVersions, DnsImplAresFlagsForNameserverRotationTest,
testing::ValuesIn(TestEnvironment::getIpVersionsForTest()),
TestUtility::ipTestParamsToString);

TEST_P(DnsImplAresFlagsForNameserverRotationTest, NameserverRotationEnabled) {
server_->addHosts("some.good.domain", {"201.134.56.7"}, RecordType::A);
ares_options opts{};
int optmask = 0;
EXPECT_EQ(ARES_SUCCESS, ares_save_options(peer_->channel(), &opts, &optmask));
EXPECT_TRUE((optmask & ARES_OPT_ROTATE) == ARES_OPT_ROTATE);
EXPECT_NE(nullptr,
resolveWithUnreferencedParameters("some.good.domain", DnsLookupFamily::Auto, true));
ares_destroy_options(&opts);
}

class DnsImplAresFlagsForNoNameserverRotationTest : public DnsImplTest {
protected:
bool tcpOnly() const override { return false; }
bool setRotateNameservers() const override { return false; }
};

// Parameterize the DNS test server socket address.
INSTANTIATE_TEST_SUITE_P(IpVersions, DnsImplAresFlagsForNoNameserverRotationTest,
testing::ValuesIn(TestEnvironment::getIpVersionsForTest()),
TestUtility::ipTestParamsToString);

TEST_P(DnsImplAresFlagsForNoNameserverRotationTest, NameserverRotationDisabled) {
server_->addHosts("some.good.domain", {"201.134.56.7"}, RecordType::A);
ares_options opts{};
int optmask = 0;
EXPECT_EQ(ARES_SUCCESS, ares_save_options(peer_->channel(), &opts, &optmask));
EXPECT_TRUE((optmask & ARES_OPT_NOROTATE) == ARES_OPT_NOROTATE);
EXPECT_NE(nullptr,
resolveWithUnreferencedParameters("some.good.domain", DnsLookupFamily::Auto, true));
ares_destroy_options(&opts);
}

} // namespace Network
} // namespace Envoy

0 comments on commit b5c15a3

Please sign in to comment.