Skip to content

Commit 765d558

Browse files
committed
ares_getaddrinfo() for AF_UNSPEC should retry if ipv6 received
We added an optimization to stop retries on other address classes on failures if one address class was received successfully. In production, however, some odd misconfigured use cases could mean an ipv6 address would be returned but the host was really only capable of connecting to ipv4 machines. We want to modify this optimization now to continue retries on ipv4 even if ipv6 was received, but NOT the other way around. It was always more likely that ipv6 resolution would cause the delays due to system issues, as the world still really only runs on ipv4... Authored-By: Brad House (@bradh352)
1 parent 91519e7 commit 765d558

File tree

2 files changed

+99
-1
lines changed

2 files changed

+99
-1
lines changed

src/lib/ares_getaddrinfo.c

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,18 @@ static void terminate_retries(const struct host_query *hquery,
481481
query->no_retries = ARES_TRUE;
482482
}
483483

484+
static ares_bool_t ai_has_ipv4(struct ares_addrinfo *ai)
485+
{
486+
struct ares_addrinfo_node *node;
487+
488+
for (node = ai->nodes; node != NULL; node = node->ai_next) {
489+
if (node->ai_family == AF_INET) {
490+
return ARES_TRUE;
491+
}
492+
}
493+
return ARES_FALSE;
494+
}
495+
484496
static void host_callback(void *arg, ares_status_t status, size_t timeouts,
485497
const ares_dns_record_t *dnsrec)
486498
{
@@ -496,7 +508,27 @@ static void host_callback(void *arg, ares_status_t status, size_t timeouts,
496508
addinfostatus =
497509
ares_parse_into_addrinfo(dnsrec, ARES_TRUE, hquery->port, hquery->ai);
498510
}
499-
if (addinfostatus == ARES_SUCCESS) {
511+
512+
/* We sent out ipv4 and ipv6 requests simultaneously. If we got a
513+
* successful ipv4 response, we want to go ahead and tell the ipv6 request
514+
* that if it fails or times out to not try again since we have the data
515+
* we need.
516+
*
517+
* Our initial implementation of this would terminate retries if we got any
518+
* successful response (ipv4 _or_ ipv6). But we did get some user-reported
519+
* issues with this that had bad system configs and odd behavior:
520+
* https://github.com/alpinelinux/docker-alpine/issues/366
521+
*
522+
* Essentially the ipv6 query succeeded but the ipv4 query failed or timed
523+
* out, and so we only returned the ipv6 address, but the host couldn't
524+
* use ipv6. If we continued to allow ipv4 retries it would have found a
525+
* server that worked and returned both address classes (this is clearly
526+
* unexpected behavior).
527+
*
528+
* At some point down the road if ipv6 actually becomes required and
529+
* reliable we can drop this ipv4 check.
530+
*/
531+
if (addinfostatus == ARES_SUCCESS && ai_has_ipv4(hquery->ai)) {
500532
terminate_retries(hquery, ares_dns_record_get_id(dnsrec));
501533
}
502534
}

test/ares-test-mock-ai.cc

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -841,6 +841,72 @@ class NoRotateMultiMockTestAI : public MockMultiServerChannelTestAI {
841841
NoRotateMultiMockTestAI() : MockMultiServerChannelTestAI(nullptr, ARES_OPT_NOROTATE) {}
842842
};
843843

844+
/* We want to terminate retries of other address classes on getaddrinfo if one
845+
* address class is returned already to return replies faster.
846+
* UPDATE: actually we want to do this only if the address class we received
847+
* was ipv4. We've seen issues if ipv6 was returned but the host was
848+
* really only capable of ipv4.
849+
*/
850+
TEST_P(NoRotateMultiMockTestAI, v4Worksv6Timesout) {
851+
std::vector<byte> nothing;
852+
853+
DNSPacket rsp4;
854+
rsp4.set_response().set_aa()
855+
.add_question(new DNSQuestion("www.example.com", T_A))
856+
.add_answer(new DNSARR("www.example.com", 0x0100, {0x01, 0x02, 0x03, 0x04}));
857+
858+
EXPECT_CALL(*servers_[0], OnRequest("www.example.com", T_A))
859+
.WillOnce(SetReply(servers_[0].get(), &rsp4));
860+
EXPECT_CALL(*servers_[0], OnRequest("www.example.com", T_AAAA))
861+
.WillOnce(SetReplyData(servers_[0].get(), nothing));
862+
863+
AddrInfoResult result;
864+
struct ares_addrinfo_hints hints = {0, 0, 0, 0};
865+
hints.ai_family = AF_UNSPEC;
866+
hints.ai_flags = ARES_AI_NOSORT;
867+
ares_getaddrinfo(channel_, "www.example.com.", NULL, &hints, AddrInfoCallback, &result);
868+
Process();
869+
EXPECT_TRUE(result.done_);
870+
EXPECT_EQ(result.status_, ARES_SUCCESS);
871+
EXPECT_THAT(result.ai_, IncludesNumAddresses(1));
872+
EXPECT_THAT(result.ai_, IncludesV4Address("1.2.3.4"));
873+
}
874+
875+
TEST_P(NoRotateMultiMockTestAI, v6Worksv4TimesoutFirst) {
876+
std::vector<byte> nothing;
877+
878+
DNSPacket rsp4;
879+
rsp4.set_response().set_aa()
880+
.add_question(new DNSQuestion("www.example.com", T_A))
881+
.add_answer(new DNSARR("www.example.com", 0x0100, {0x01, 0x02, 0x03, 0x04}));
882+
883+
DNSPacket rsp6;
884+
rsp6.set_response().set_aa()
885+
.add_question(new DNSQuestion("www.example.com", T_AAAA))
886+
.add_answer(new DNSAaaaRR("www.example.com", 100,
887+
{0x21, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
888+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03}));
889+
890+
EXPECT_CALL(*servers_[0], OnRequest("www.example.com", T_A))
891+
.WillOnce(SetReplyData(servers_[0].get(), nothing));
892+
EXPECT_CALL(*servers_[0], OnRequest("www.example.com", T_AAAA))
893+
.WillOnce(SetReply(servers_[0].get(), &rsp6));
894+
EXPECT_CALL(*servers_[1], OnRequest("www.example.com", T_A))
895+
.WillOnce(SetReply(servers_[1].get(), &rsp4));
896+
897+
AddrInfoResult result;
898+
struct ares_addrinfo_hints hints = {0, 0, 0, 0};
899+
hints.ai_family = AF_UNSPEC;
900+
hints.ai_flags = ARES_AI_NOSORT;
901+
ares_getaddrinfo(channel_, "www.example.com.", NULL, &hints, AddrInfoCallback, &result);
902+
Process();
903+
EXPECT_TRUE(result.done_);
904+
EXPECT_EQ(result.status_, ARES_SUCCESS);
905+
EXPECT_THAT(result.ai_, IncludesNumAddresses(2));
906+
EXPECT_THAT(result.ai_, IncludesV4Address("1.2.3.4"));
907+
EXPECT_THAT(result.ai_, IncludesV6Address("2121:0000:0000:0000:0000:0000:0000:0303"));
908+
909+
}
844910

845911
TEST_P(NoRotateMultiMockTestAI, ThirdServer) {
846912
struct ares_options opts;

0 commit comments

Comments
 (0)