RFC 1123 and RFC 2181 recommend retrying a truncated DNS response
over a transport capable of carrying a larger message. HAProxy instead
treats TC as a resolver error unless the nameserver is configured to
use TCP for every query.

Give unprefixed IPv4, IPv6 and hostname nameservers both a datagram and
a stream server. Queries use UDP first and the existing stream code
opens TCP connections lazily. Explicit datagram prefixes remain
UDP-only, explicit stream prefixes remain TCP-only, and nameservers
loaded from resolv.conf gain the default fallback behavior.

When a valid UDP response is truncated, rebuild its query over TCP with
the same ID, question type and nameserver. Taking the type from the
response preserves late A or AAAA responses received after a query-type
switch. Queueing the fallback refreshes the retry deadline without
consuming a retry or initiating another A/AAAA switch.

A truncated SRV response can still be partially processed when no TCP
fallback can be initiated for that response. This preserves existing
behavior for explicit UDP and TCP nameservers and processes a partial
SRV response received during TCP fallback. Other truncated TCP
responses follow the normal error path, and no TCP response triggers
another fallback.

Failed queue operations use the existing send-error path, while
connection failures are handled by the resolver timeout and retry path.

Add a tcp_fallback counter to the CLI, resolver statistics and
Prometheus output. It counts fallback queries successfully queued to the
stream server.
---
Changes since the previously posted patch:
  - retain partial truncated SRV processing whenever TCP fallback cannot
    be initiated for the response.

 addons/promex/README          |   1 +
 doc/configuration.txt         |  25 +++-
 include/haproxy/dns-t.h       |   5 +-
 include/haproxy/dns.h         |   6 +
 include/haproxy/resolvers-t.h |   1 +
 src/resolvers.c               | 230 +++++++++++++++++++++++++++-------
 6 files changed, 215 insertions(+), 53 deletions(-)

diff --git a/addons/promex/README b/addons/promex/README
index 9ed37debe..978052ec8 100644
--- a/addons/promex/README
+++ b/addons/promex/README
@@ -430,5 +430,6 @@ listed below. Metrics from extra counters are not listed.
 | haproxy_resolver_other                             |
 | haproxy_resolver_invalid                           |
 | haproxy_resolver_too_big                           |
+| haproxy_resolver_tcp_fallback                      |
 | haproxy_resolver_outdated                          |
 +----------------------------------------------------+
diff --git a/doc/configuration.txt b/doc/configuration.txt
index 26e930f4b..bb80e83be 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -20391,10 +20391,24 @@ accepted_payload_size <nb>
 
 nameserver <name> <address>[:port] [param*]
   Used to configure a nameserver. <name> of the nameserver should ne unique.
-  By default the <address> is considered of type datagram. This means if an
-  IPv4 or IPv6 is configured without special address prefixes (paragraph 11.)
-  the UDP protocol will be used.  If an stream protocol address prefix is used,
-  the nameserver will be considered as a stream server (TCP for instance) and
+  By default an IPv4, IPv6 or hostname <address> is queried over UDP. When a
+  valid UDP response has its truncated (TC) flag set, the same query is retried
+  over TCP against the same address and port. The TCP connection is established
+  lazily, only after a truncated response is received, and uses the regular DNS
+  stream connection pooling, pipelining, timeouts and idle connection cleanup.
+
+  An explicit datagram address prefix such as "udp@", "udp4@", "udp6@" or
+  "dgram+" forces UDP-only operation and disables TCP fallback. An explicit
+  stream address prefix such as "tcp@" forces TCP-only operation.
+
+  A failure to connect or send over TCP is handled by the regular resolver
+  timeout and retry mechanism. Starting TCP fallback does not consume a retry
+  or switch the query between A and AAAA. A truncated response received over
+  TCP follows the normal resolver error path and does not start another
+  fallback.
+
+  If a stream protocol address prefix is used, the nameserver will be
+  considered as a stream server (TCP for instance) and
   "server" parameters found in 5.2 paragraph which are relevant for DNS
   resolving will be considered.  Note: currently, in TCP mode, 4 queries are
   pipelined on the same connections. A batch of idle connections are removed
@@ -20404,7 +20418,8 @@ nameserver <name> <address>[:port] [param*]
 parse-resolv-conf
   Adds all nameservers found in /etc/resolv.conf to this resolvers nameservers
   list. Ordered as if each nameserver in /etc/resolv.conf was individually
-  placed in the resolvers section in place of this directive.
+  placed in the resolvers section in place of this directive. These nameservers
+  use UDP normally and retry truncated responses over TCP.
 
 hold <status> <period>
   Upon receiving the DNS response <status>, determines whether a server's state
diff --git a/include/haproxy/dns-t.h b/include/haproxy/dns-t.h
index cf3c21043..39dac4b85 100644
--- a/include/haproxy/dns-t.h
+++ b/include/haproxy/dns-t.h
@@ -154,7 +154,7 @@ struct dns_nameserver {
 
        int (*process_responses)(struct dns_nameserver *ns, enum 
dns_server_type type); /* callback used to process responses */
        struct dns_dgram_server *dgram;  /* used for dgram dns */
-       struct dns_stream_server *stream; /* used for tcp dns */
+       struct dns_stream_server *stream; /* used for stream DNS and TCP 
fallback */
 
        EXTRA_COUNTERS(extra_counters);
        char *extra_counters_storage;    /* storage used for extra_counters 
above */
@@ -184,7 +184,8 @@ struct dns_counters {
                        long long invalid;      /* - malformed DNS response */
                        long long too_big;      /* - too big response */
                        long long outdated;     /* - outdated response (server 
slower than the other ones) */
-                       long long truncated;    /* - truncated response */;
+                       long long truncated;    /* - truncated response */
+                       long long tcp_fallback; /* - truncated response retried 
over TCP */
                } resolver;
        } app;         /* application specific counteurs */
 };
diff --git a/include/haproxy/dns.h b/include/haproxy/dns.h
index c929415d0..13b38b424 100644
--- a/include/haproxy/dns.h
+++ b/include/haproxy/dns.h
@@ -43,4 +43,10 @@ static inline int dns_server_type_is_stream(enum 
dns_server_type type)
        return type == DNS_SERVER_STREAM;
 }
 
+/* Returns nonzero if <ns> owns both servers needed for TCP fallback. */
+static inline int dns_nameserver_has_fallback(const struct dns_nameserver *ns)
+{
+       return ns->dgram && ns->stream;
+}
+
 #endif // _HAPROXY_DNS_H
diff --git a/include/haproxy/resolvers-t.h b/include/haproxy/resolvers-t.h
index cc923c851..6b32c7bc8 100644
--- a/include/haproxy/resolvers-t.h
+++ b/include/haproxy/resolvers-t.h
@@ -218,6 +218,7 @@ struct resolv_resolution {
        int                   try;                 /* current resolution try */
        int                   nb_queries;          /* count number of queries 
sent */
        int                   nb_responses;        /* count number of responses 
received */
+       int                   nb_tcp_fallback;     /* count TCP fallback 
responses still outstanding */
 
        struct resolv_response response;           /* structure hosting the DNS 
response */
        struct resolv_query_item response_query_records[DNS_MAX_QUERY_RECORDS]; 
/* <response> query records */
diff --git a/src/resolvers.c b/src/resolvers.c
index e9d97a67a..1bb98ec3a 100644
--- a/src/resolvers.c
+++ b/src/resolvers.c
@@ -96,28 +96,30 @@ enum {
        RSLV_STAT_INVALID,
        RSLV_STAT_TOO_BIG,
        RSLV_STAT_TRUNCATED,
+       RSLV_STAT_TCP_FALLBACK,
        RSLV_STAT_OUTDATED,
        RSLV_STAT_END,
 };
 
 static struct stat_col resolv_stats[] = {
-       [RSLV_STAT_ID]          = { .name = "id",          .desc = "ID" },
-       [RSLV_STAT_PID]         = { .name = "pid",         .desc = "Parent ID" 
},
-       [RSLV_STAT_SENT]        = { .name = "sent",        .desc = "Sent" },
-       [RSLV_STAT_SND_ERROR]   = { .name = "send_error",  .desc = "Send error" 
},
-       [RSLV_STAT_VALID]       = { .name = "valid",       .desc = "Valid" },
-       [RSLV_STAT_UPDATE]      = { .name = "update",      .desc = "Update" },
-       [RSLV_STAT_CNAME]       = { .name = "cname",       .desc = "CNAME" },
-       [RSLV_STAT_CNAME_ERROR] = { .name = "cname_error", .desc = "CNAME 
error" },
-       [RSLV_STAT_ANY_ERR]     = { .name = "any_err",     .desc = "Any errors" 
},
-       [RSLV_STAT_NX]          = { .name = "nx",          .desc = "NX" },
-       [RSLV_STAT_TIMEOUT]     = { .name = "timeout",     .desc = "Timeout" },
-       [RSLV_STAT_REFUSED]     = { .name = "refused",     .desc = "Refused" },
-       [RSLV_STAT_OTHER]       = { .name = "other",       .desc = "Other" },
-       [RSLV_STAT_INVALID]     = { .name = "invalid",     .desc = "Invalid" },
-       [RSLV_STAT_TOO_BIG]     = { .name = "too_big",     .desc = "Too big" },
-       [RSLV_STAT_TRUNCATED]   = { .name = "truncated",   .desc = "Truncated" 
},
-       [RSLV_STAT_OUTDATED]    = { .name = "outdated",    .desc = "Outdated" },
+       [RSLV_STAT_ID]           = { .name = "id",           .desc = "ID" },
+       [RSLV_STAT_PID]          = { .name = "pid",          .desc = "Parent 
ID" },
+       [RSLV_STAT_SENT]         = { .name = "sent",         .desc = "Sent" },
+       [RSLV_STAT_SND_ERROR]    = { .name = "send_error",   .desc = "Send 
error" },
+       [RSLV_STAT_VALID]        = { .name = "valid",        .desc = "Valid" },
+       [RSLV_STAT_UPDATE]       = { .name = "update",       .desc = "Update" },
+       [RSLV_STAT_CNAME]        = { .name = "cname",        .desc = "CNAME" },
+       [RSLV_STAT_CNAME_ERROR]  = { .name = "cname_error",  .desc = "CNAME 
error" },
+       [RSLV_STAT_ANY_ERR]      = { .name = "any_err",      .desc = "Any 
errors" },
+       [RSLV_STAT_NX]           = { .name = "nx",           .desc = "NX" },
+       [RSLV_STAT_TIMEOUT]      = { .name = "timeout",      .desc = "Timeout" 
},
+       [RSLV_STAT_REFUSED]      = { .name = "refused",      .desc = "Refused" 
},
+       [RSLV_STAT_OTHER]        = { .name = "other",        .desc = "Other" },
+       [RSLV_STAT_INVALID]      = { .name = "invalid",      .desc = "Invalid" 
},
+       [RSLV_STAT_TOO_BIG]      = { .name = "too_big",      .desc = "Too big" 
},
+       [RSLV_STAT_TRUNCATED]    = { .name = "truncated",    .desc = 
"Truncated" },
+       [RSLV_STAT_TCP_FALLBACK] = { .name = "tcp_fallback", .desc = "TCP 
fallback" },
+       [RSLV_STAT_OUTDATED]     = { .name = "outdated",     .desc = "Outdated" 
},
 };
 
 static struct dns_counters dns_counters;
@@ -185,6 +187,9 @@ static int resolv_fill_stats(struct stats_module *mod, 
struct extra_counters *ct
                case RSLV_STAT_TRUNCATED:
                        metric = mkf_u64(FN_GAUGE, 
counters->app.resolver.truncated);
                        break;
+               case RSLV_STAT_TCP_FALLBACK:
+                       metric = mkf_u64(FN_GAUGE, 
counters->app.resolver.tcp_fallback);
+                       break;
                case RSLV_STAT_OUTDATED:
                        metric = mkf_u64(FN_GAUGE, 
counters->app.resolver.outdated);
                        break;
@@ -454,9 +459,10 @@ static int resolv_send_query(struct resolv_resolution 
*resolution)
        int len;
 
        /* Update resolution */
-       resolution->nb_queries   = 0;
-       resolution->nb_responses = 0;
-       resolution->last_query   = now_ms;
+       resolution->nb_queries      = 0;
+       resolution->nb_responses    = 0;
+       resolution->nb_tcp_fallback = 0;
+       resolution->last_query      = now_ms;
 
        len = resolv_build_query(resolution->query_id, resolution->query_type,
                              resolvers->accepted_payload_size,
@@ -483,6 +489,34 @@ static int resolv_send_query(struct resolv_resolution 
*resolution)
        return 0;
 }
 
+/* Retries query type <query_type> over the stream server of nameserver <ns>.
+ * Returns 1 if the query was queued and 0 otherwise.
+ */
+static int resolv_send_tcp_fallback(struct resolv_resolution *resolution,
+                                    struct dns_nameserver *ns, int query_type)
+{
+       struct resolvers *resolvers = resolution->resolvers;
+       int len;
+
+       len = resolv_build_query(resolution->query_id, query_type,
+                               resolvers->accepted_payload_size,
+                               resolution->hostname_dn, 
resolution->hostname_dn_len,
+                               trash.area, trash.size);
+       if (len < 0)
+               return 0;
+
+       if (dns_send_nameserver(ns, DNS_SERVER_STREAM, trash.area, len) < 0)
+               return 0;
+
+       resolution->nb_queries++;
+       resolution->nb_tcp_fallback++;
+       resolution->last_query = now_ms;
+       LIST_DEL_INIT(&resolution->list);
+       LIST_APPEND(&resolvers->resolutions.curr, &resolution->list);
+       ns->counters->app.resolver.tcp_fallback++;
+       return 1;
+}
+
 /* Prepares and sends a DNS resolution. It returns 1 if the query was sent, 0 
if
  * skipped and -1 if an error occurred.
  */
@@ -577,6 +611,7 @@ static void resolv_reset_resolution(struct 
resolv_resolution *resolution)
        resolution->last_resolution = now_ms;
        resolution->nb_queries      = 0;
        resolution->nb_responses    = 0;
+       resolution->nb_tcp_fallback = 0;
        resolution->query_type      = resolution->prefered_query_type;
 
        /* clean up query id */
@@ -1009,12 +1044,14 @@ static void resolv_check_response(struct 
resolv_resolution *res)
  *
  * The result is stored in <resolution>' response, buf_response,
  * response_query_records and response_answer_records members.
+ * A truncated SRV response may be parsed when <allow_truncated_srv> is set.
  *
  * This function returns one of the RSLV_RESP_* code to indicate the type of
  * error found.
  */
 static int resolv_validate_dns_response(unsigned char *resp, unsigned char 
*bufend,
-                                        struct resolv_resolution *resolution, 
int max_answer_records)
+                                        struct resolv_resolution *resolution, 
int max_answer_records,
+                                        int allow_truncated_srv)
 {
        unsigned char *reader;
        char *previous_dname, tmpname[DNS_MAX_NAME_SIZE];
@@ -1148,10 +1185,20 @@ static int resolv_validate_dns_response(unsigned char 
*resp, unsigned char *bufe
        query->class = reader[0] * 256 + reader[1];
        reader += 2;
 
+       /* A and AAAA responses may arrive after the query type switched. */
+       if (query->class != DNS_RCLASS_IN ||
+           (query->type != resolution->query_type &&
+            !((query->type == DNS_RTYPE_A || query->type == DNS_RTYPE_AAAA) &&
+              (resolution->query_type == DNS_RTYPE_A ||
+               resolution->query_type == DNS_RTYPE_AAAA))))
+               goto invalid_resp;
+
        /* TRUNCATED flag must be checked after we could read the query type
         * because a TRUNCATED SRV query type response can still be exploited
+        * when no TCP fallback can be performed.
         */
-       if (query->type != DNS_RTYPE_SRV && flags & DNS_FLAG_TRUNCATED) {
+       if ((flags & DNS_FLAG_TRUNCATED) &&
+           (query->type != DNS_RTYPE_SRV || !allow_truncated_srv)) {
                cause = RSLV_RESP_TRUNCATED;
                goto return_error;
        }
@@ -2377,11 +2424,16 @@ static int resolv_process_responses(struct 
dns_nameserver *ns, enum dns_server_t
 
                /* known query id means a resolution in progress */
                res = eb32_entry(eb, struct resolv_resolution, qid);
+               if (dns_server_type_is_stream(type) &&
+                   dns_nameserver_has_fallback(ns) && res->nb_tcp_fallback)
+                       res->nb_tcp_fallback--;
                /* number of responses received */
                res->nb_responses++;
 
                max_answer_records = (max_payload_size - DNS_HEADER_SIZE) / 
DNS_MIN_RECORD_SIZE;
-               dns_resp = resolv_validate_dns_response(buf, bufend, res, 
max_answer_records);
+               dns_resp = resolv_validate_dns_response(buf, bufend, res, 
max_answer_records,
+                                                      
!(dns_server_type_is_dgram(type) &&
+                                                        
dns_nameserver_has_fallback(ns)));
 
                switch (dns_resp) {
                        case RSLV_RESP_VALID:
@@ -2417,6 +2469,11 @@ static int resolv_process_responses(struct 
dns_nameserver *ns, enum dns_server_t
                        case RSLV_RESP_TRUNCATED:
                                res->status = RSLV_STATUS_OTHER;
                                ns->counters->app.resolver.truncated++;
+                               if (dns_server_type_is_dgram(type) &&
+                                   dns_nameserver_has_fallback(ns) &&
+                                   resolv_send_tcp_fallback(res, ns,
+                                                           
res->response_query_records[0].type))
+                                       continue;
                                break;
 
                        case RSLV_RESP_NO_EXPECTED_RECORD:
@@ -2560,8 +2617,12 @@ struct task *process_resolvers(struct task *t, void 
*context, unsigned int state
                }
                else {
                        /* Otherwise resend the DNS query and requeue the 
resolution */
-                       if (!res->nb_responses || res->prefered_query_type != 
res->query_type) {
-                               /* No response received (a real timeout) or 
fallback already done */
+                       if (!res->nb_responses || res->nb_tcp_fallback ||
+                           res->prefered_query_type != res->query_type) {
+                               /* No response was received, a TCP fallback 
response is
+                                * still outstanding, or the query-type 
fallback was
+                                * already performed.
+                                */
                                res->query_type = res->prefered_query_type;
                                res->try--;
                        }
@@ -3109,21 +3170,22 @@ static int 
cli_io_handler_dump_resolvers_to_buffer(struct appctx *appctx)
                        list_for_each_entry_from(ns, &resolvers->nameservers, 
list) {
                                chunk_reset(&trash);
                                chunk_appendf(&trash, " nameserver %s:\n", 
ns->id);
-                               chunk_appendf(&trash, "  sent:        %lld\n", 
ns->counters->sent);
-                               chunk_appendf(&trash, "  snd_error:   %lld\n", 
ns->counters->snd_error);
-                               chunk_appendf(&trash, "  valid:       %lld\n", 
ns->counters->app.resolver.valid);
-                               chunk_appendf(&trash, "  update:      %lld\n", 
ns->counters->app.resolver.update);
-                               chunk_appendf(&trash, "  cname:       %lld\n", 
ns->counters->app.resolver.cname);
-                               chunk_appendf(&trash, "  cname_error: %lld\n", 
ns->counters->app.resolver.cname_error);
-                               chunk_appendf(&trash, "  any_err:     %lld\n", 
ns->counters->app.resolver.any_err);
-                               chunk_appendf(&trash, "  nx:          %lld\n", 
ns->counters->app.resolver.nx);
-                               chunk_appendf(&trash, "  timeout:     %lld\n", 
ns->counters->app.resolver.timeout);
-                               chunk_appendf(&trash, "  refused:     %lld\n", 
ns->counters->app.resolver.refused);
-                               chunk_appendf(&trash, "  other:       %lld\n", 
ns->counters->app.resolver.other);
-                               chunk_appendf(&trash, "  invalid:     %lld\n", 
ns->counters->app.resolver.invalid);
-                               chunk_appendf(&trash, "  too_big:     %lld\n", 
ns->counters->app.resolver.too_big);
-                               chunk_appendf(&trash, "  truncated:   %lld\n", 
ns->counters->app.resolver.truncated);
-                               chunk_appendf(&trash, "  outdated:    %lld\n",  
ns->counters->app.resolver.outdated);
+                               chunk_appendf(&trash, "  sent:         %lld\n", 
ns->counters->sent);
+                               chunk_appendf(&trash, "  snd_error:    %lld\n", 
ns->counters->snd_error);
+                               chunk_appendf(&trash, "  valid:        %lld\n", 
ns->counters->app.resolver.valid);
+                               chunk_appendf(&trash, "  update:       %lld\n", 
ns->counters->app.resolver.update);
+                               chunk_appendf(&trash, "  cname:        %lld\n", 
ns->counters->app.resolver.cname);
+                               chunk_appendf(&trash, "  cname_error:  %lld\n", 
ns->counters->app.resolver.cname_error);
+                               chunk_appendf(&trash, "  any_err:      %lld\n", 
ns->counters->app.resolver.any_err);
+                               chunk_appendf(&trash, "  nx:           %lld\n", 
ns->counters->app.resolver.nx);
+                               chunk_appendf(&trash, "  timeout:      %lld\n", 
ns->counters->app.resolver.timeout);
+                               chunk_appendf(&trash, "  refused:      %lld\n", 
ns->counters->app.resolver.refused);
+                               chunk_appendf(&trash, "  other:        %lld\n", 
ns->counters->app.resolver.other);
+                               chunk_appendf(&trash, "  invalid:      %lld\n", 
ns->counters->app.resolver.invalid);
+                               chunk_appendf(&trash, "  too_big:      %lld\n", 
ns->counters->app.resolver.too_big);
+                               chunk_appendf(&trash, "  truncated:    %lld\n", 
ns->counters->app.resolver.truncated);
+                               chunk_appendf(&trash, "  tcp_fallback: %lld\n", 
ns->counters->app.resolver.tcp_fallback);
+                               chunk_appendf(&trash, "  outdated:     %lld\n", 
ns->counters->app.resolver.outdated);
                                if (applet_putchk(appctx, &trash) == -1)
                                        goto full;
                                ctx->ns = ns;
@@ -3491,6 +3553,53 @@ void resolvers_setup_proxy(struct proxy *px)
        px->options2 |= PR_O2_INDEPSTR | PR_O2_SMARTCON;
 }
 
+/* Adds the stream transport used for TCP fallback, named <name> and targeting
+ * <sk>, to <ns>. Errors are stored in <errmsg> when it is non-NULL. Returns a
+ * combination of ERR_* flags.
+ */
+static int resolv_add_tcp_fallback(struct dns_nameserver *ns, const char *file,
+                                   int linenum, const char *name,
+                                   const struct sockaddr_storage *sk,
+                                   char **errmsg)
+{
+       char *address = NULL;
+       char *tcp_addr = NULL;
+       char *tcp_args[5] = { "nameserver", NULL, NULL, "", "" };
+       int err_code = 0;
+
+       address = sa2str(sk, get_host_port(sk), 0);
+       if (!address || !memprintf(&tcp_addr, "tcp@%s", address)) {
+               if (errmsg)
+                       memprintf(errmsg, "parsing [%s:%d] : out of memory.", 
file, linenum);
+               err_code |= ERR_ALERT | ERR_ABORT;
+               goto out;
+       }
+
+       tcp_args[1] = (char *)name;
+       tcp_args[2] = tcp_addr;
+       err_code |= parse_server(file, linenum, tcp_args, curr_resolvers->px, 
NULL,
+                                
SRV_PARSE_PARSE_ADDR|SRV_PARSE_INITIAL_RESOLVE);
+       if (err_code & (ERR_FATAL|ERR_ABORT)) {
+               if (errmsg && !*errmsg)
+                       memprintf(errmsg, "parsing [%s:%d] : failed to 
initialize TCP fallback for nameserver '%s'.",
+                                 file, linenum, name);
+               err_code |= ERR_ABORT;
+               goto out;
+       }
+
+       if (dns_stream_init(ns, proxy_last_server(curr_resolvers->px)) < 0) {
+               if (errmsg)
+                       memprintf(errmsg, "parsing [%s:%d] : out of memory.", 
file, linenum);
+               err_code |= ERR_ALERT | ERR_ABORT;
+               goto out;
+       }
+
+ out:
+       ha_free(&address);
+       ha_free(&tcp_addr);
+       return err_code;
+}
+
 static int parse_resolve_conf(char **errmsg, char **warnmsg)
 {
        struct dns_nameserver *newnameserver = NULL;
@@ -3591,6 +3700,14 @@ static int parse_resolve_conf(char **errmsg, char 
**warnmsg)
                        goto resolv_out;
                }
 
+               err_code |= resolv_add_tcp_fallback(newnameserver, 
"/etc/resolv.conf",
+                                                  resolv_linenum, address, sk, 
errmsg);
+               if (err_code & (ERR_FATAL|ERR_ABORT)) {
+                       dns_nameserver_deinit(newnameserver);
+                       free(newnameserver);
+                       goto resolv_out;
+               }
+
                newnameserver->conf.file = strdup("/etc/resolv.conf");
                if (newnameserver->conf.file == NULL) {
                        if (errmsg)
@@ -3757,7 +3874,9 @@ int cfg_parse_resolvers(const char *file, int linenum, 
char **args, int kwm)
        else if (strcmp(args[0], "nameserver") == 0) { /* nameserver definition 
*/
                struct dns_nameserver *newnameserver = NULL;
                struct sockaddr_storage *sk;
+               struct net_addr_type addr_type;
                int port1, port2;
+               int add_fallback = 0;
                struct protocol *proto;
 
                if (!*args[2]) {
@@ -3785,7 +3904,7 @@ int cfg_parse_resolvers(const char *file, int linenum, 
char **args, int kwm)
                        }
                }
 
-               sk = str2sa_range(args[2], NULL, &port1, &port2, NULL, &proto, 
NULL,
+               sk = str2sa_range(args[2], NULL, &port1, &port2, NULL, &proto, 
&addr_type,
                                  &errmsg, NULL, NULL, NULL,
                                  PA_O_RESOLVE | PA_O_PORT_OK | PA_O_PORT_MAND 
| PA_O_DGRAM | PA_O_STREAM | PA_O_DEFAULT_DGRAM);
                if (!sk) {
@@ -3794,6 +3913,10 @@ int cfg_parse_resolvers(const char *file, int linenum, 
char **args, int kwm)
                        goto out;
                }
 
+               add_fallback = (addr_type.xprt_type == PROTO_TYPE_DGRAM &&
+                               !addr_type.explicit_type &&
+                               (sk->ss_family == AF_INET || sk->ss_family == 
AF_INET6));
+
                if ((newnameserver = calloc(1, sizeof(*newnameserver))) == 
NULL) {
                        ha_alert("parsing [%s:%d] : out of memory.\n", file, 
linenum);
                        err_code |= ERR_ALERT | ERR_ABORT;
@@ -3816,11 +3939,26 @@ int cfg_parse_resolvers(const char *file, int linenum, 
char **args, int kwm)
                                goto out;
                        }
                }
-               else if (dns_dgram_init(newnameserver, sk) < 0) {
-                       ha_alert("parsing [%s:%d] : out of memory.\n", file, 
linenum);
-                       err_code |= ERR_ALERT | ERR_ABORT;
-                       free(newnameserver);
-                       goto out;
+               else {
+                       if (dns_dgram_init(newnameserver, sk) < 0) {
+                               ha_alert("parsing [%s:%d] : out of memory.\n", 
file, linenum);
+                               err_code |= ERR_ALERT | ERR_ABORT;
+                               free(newnameserver);
+                               goto out;
+                       }
+
+                       if (add_fallback) {
+                               err_code |= 
resolv_add_tcp_fallback(newnameserver, file,
+                                                                   linenum, 
args[1], sk, &errmsg);
+                               if (err_code & (ERR_FATAL|ERR_ABORT)) {
+                                       if (errmsg)
+                                               ha_alert("%s\n", errmsg);
+                                       err_code |= ERR_ABORT;
+                                       dns_nameserver_deinit(newnameserver);
+                                       free(newnameserver);
+                                       goto out;
+                               }
+                       }
                }
 
                if ((newnameserver->conf.file = strdup(file)) == NULL) {
-- 
2.55.0

Reply via email to