]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
BUG/MINOR: resolvers: ensure fair round robin iteration
authorDamien Claisse <d.claisse@criteo.com>
Wed, 29 Oct 2025 09:56:34 +0000 (09:56 +0000)
committerWilly Tarreau <w@1wt.eu>
Sun, 2 Nov 2025 16:28:32 +0000 (17:28 +0100)
Previous fixes restored round robin iteration, but an imbalance remains
when the response tree contains record types other than A or AAAA. Let's
take the following example: the DNS answers two A records and a CNAME.
The response "tree" (which is actually flat, more like a list) may look
as follows, ordered by hash:
- 1st item: first A record with IP 1
- 2nd item: second A record with IP 2
- 3rd item: CNAME record
As a consequence, resolv_get_ip_from_response will iterate as follows,
while the TTL is still valid:
- 1st call: DNS request is done, response tree is created, iteration
  starts at the first item, IP 1 is returned.
- 2nd call: cached response tree is used, iteration starts at the second
  item, IP 2 is returned.
- 3rd call: cached response tree is used, iteration starts at the third
  item, but it's a CNAME, so we continue to the next item, which restarts
  iteration at the first item, and IP 1 is returned.
- 4th call: cached response tree is used and iteration restarts at the
  beginning, returning IP 1 again.
The 1-2-1-1-2-1-1-2 sequence will repeat, so IP 1 will be used twice as
often as IP 2, creating a strong imbalance. Even with more IP addresses,
the first one by hashing order in the tree will always receive twice the
traffic of the others.
To fix this, set the next iteration item to the one following the selected
IP record, if any. This ensures we never use the same IP twice in a row.

This commit should be backported where 3023e9819 ("BUG/MINOR: resolvers:
Restore round-robin selection on records in DNS answers") is, so as far
as 2.6.

src/resolvers.c

index c8be0cd983cfc773d4598076ccfe6f131d6bc15d..1ce1774c1e4e8917ed2beb0f7e8bf95837536bbd 100644 (file)
@@ -1647,7 +1647,6 @@ int resolv_get_ip_from_response(struct resolv_response *r_res,
         */
        eb32 = (!r_res->next) ? eb32_first(&r_res->answer_tree) : r_res->next;
        end = eb32;
-       r_res->next = eb32_next(eb32); /* get node for the next lookup */
        do {
                void *ip;
                unsigned char ip_type;
@@ -1733,6 +1732,7 @@ int resolv_get_ip_from_response(struct resolv_response *r_res,
                 * break the parsing. Implicitly, this score is reached the ip
                 * selected is the current ip. */
                if (score > max_score) {
+                       r_res->next = eb32_next(eb32); /* get node for the next lookup */
                        if (ip_type == AF_INET)
                                newip4 = ip;
                        else