Aurelien DARRAGON 5288b39011 BUG/MINOR: dns: prevent ds accumulation within dss
when dns session callback (dns_session_release()) is called upon error
(ie: when some pending queries were not sent), we try our best to
re-create the applet in order to preserve the pending queries and give
them a chance to be retried. This is done at the end of
dns_session_release().

However, doing so exposes to an issue: if the error preventing queries
from being sent is still encountered over and over the dns session could
stay there indefinitely. Meanwhile, other dns sessions may be created on
the same dns_stream_server periodically. If previous failing dns sessions
don't terminate but we also keep creating new ones, we end up accumulating
failing sessions on a given dns_stream_server, which can eventually cause
ressource shortage.

This issue was found when trying to address ("BUG/MINOR: dns: add tempo
between 2 connection attempts for dns servers")

To fix it, we track the number of failed consecutive sessions for a given
dns server. When we reach the threshold (set to 100), we consider that the
link to the dns server is broken (at least temporarily) and we force
dns_session_new() to fail, so that we stop creating new sessions until one
of the existing one eventually succeeds.

A workaround for this fix consists in setting the "maxconn" parameter on
nameserver directive (under resolvers section) to a reasonnable value so
that no more than "maxconn" sessions may co-exist on the same server at
a given time.

This may be backported to all stable versions.
("CLEANUP: dns: remove unused dns_stream_server struct member") may be
backported to ease the backport.
2025-04-29 21:20:54 +02:00

187 lines
6.3 KiB
C

/*
* include/haproxy/dns-t.h
* This file provides structures and types for DNS.
*
* Copyright (C) 2014 Baptiste Assmann <bedis9@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, version 2.1
* exclusively.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef _HAPROXY_DNS_T_H
#define _HAPROXY_DNS_T_H
#include <import/ebtree-t.h>
#include <haproxy/connection-t.h>
#include <haproxy/buf-t.h>
#include <haproxy/dgram-t.h>
#include <haproxy/dns_ring-t.h>
#include <haproxy/obj_type-t.h>
#include <haproxy/stats-t.h>
#include <haproxy/task-t.h>
#include <haproxy/thread.h>
/* DNS header size */
#define DNS_HEADER_SIZE ((int)sizeof(struct dns_header))
/* max pending requests per stream */
#define DNS_STREAM_MAX_PIPELINED_REQ 4
#define DNS_TCP_MSG_MAX_SIZE 65535
#define DNS_TCP_MSG_RING_MAX_SIZE (1 + 1 + 3 + DNS_TCP_MSG_MAX_SIZE) // varint_bytes(DNS_TCP_MSG_MAX_SIZE) == 3
/* threshold to consider that the link to dns server is failing
* and we should stop creating new sessions
*/
#define DNS_MAX_DSS_CONSECUTIVE_ERRORS 100
/* DNS request or response header structure */
struct dns_header {
uint16_t id;
uint16_t flags;
uint16_t qdcount;
uint16_t ancount;
uint16_t nscount;
uint16_t arcount;
} __attribute__ ((packed));
/* short structure to describe a DNS question */
/* NOTE: big endian structure */
struct dns_question {
unsigned short qtype; /* question type */
unsigned short qclass; /* query class */
} __attribute__ ((packed));
/* NOTE: big endian structure */
struct dns_additional_record {
uint8_t name; /* domain name, must be 0 (RFC 6891) */
uint16_t type; /* record type DNS_RTYPE_OPT (41) */
uint16_t udp_payload_size; /* maximum size accepted for the response */
uint32_t extension; /* extended rcode and flags, not used for now */
uint16_t data_length; /* data length */
/* as of today, we don't support yet edns options, that said I already put a
* placeholder here for this purpose. We may need to define a dns_option_record
* structure which itself should point to different type of data, based on the
* extension set (client subnet, tcp keepalive, etc...)*/
} __attribute__ ((packed));
/* Structure describing a name server used during name resolution.
* A name server belongs to a resolvers section.
*/
struct dns_stream_server {
struct server *srv;
struct dns_ring *ring_req;
int consecutive_errors; /* number of errors since last successful query (atomically updated without lock) */
int maxconn;
int idle_conns;
int cur_conns;
int max_active_conns;
size_t ofs_req; // ring buffer reader offset
size_t ofs_rsp; // ring buffer reader offset
struct task *task_req; /* req conn management */
struct task *task_rsp; /* rsp management */
struct task *task_idle; /* handle idle sess */
struct list free_sess;
struct list idle_sess;
struct list wait_sess;
__decl_thread(HA_SPINLOCK_T lock); // lock to protect current struct
};
struct dns_dgram_server {
struct dgram_conn conn; /* transport layer */
struct dns_ring *ring_req;
size_t ofs_req; // ring buffer reader offset
};
struct dns_query {
struct eb32_node qid;
uint16_t original_qid;
int expire;
struct list list;
};
struct dns_session {
struct appctx *appctx; // appctx of current session
struct dns_stream_server *dss;
uint16_t tx_msg_offset;
int nb_queries;
int onfly_queries;
int query_counter;
struct list list;
struct list waiter;
struct list queries;
struct task *task_exp;
struct eb_root query_ids; /* tree to quickly lookup/retrieve query ids currently in use */
size_t ofs; // ring buffer reader offset
struct dns_ring ring;
struct {
uint16_t len;
uint16_t offset;
char *area;
} rx_msg;
unsigned char *tx_ring_area;
int shutdown;
};
/* Structure describing a name server
*/
struct dns_nameserver {
char *id; /* nameserver unique identifier */
void *parent;
unsigned int puid; /* parent-unique numeric id */
struct {
const char *file; /* file where the section appears */
int line; /* line where the section appears */
} conf; /* config information */
int (*process_responses)(struct dns_nameserver *ns); /* callback used to process responses */
struct dns_dgram_server *dgram; /* used for dgram dns */
struct dns_stream_server *stream; /* used for tcp dns */
EXTRA_COUNTERS(extra_counters);
struct dns_counters *counters;
struct list list; /* nameserver chained list */
};
/* mixed dns and resolver counters, we will have to split them */
struct dns_counters {
char *id; /* nameserver id */
char *pid; /* parent resolver id */
unsigned int ns_puid; /* nameserver numerical id (ns->puid) */
long long sent; /* - queries sent */
long long snd_error; /* - sending errors */
union {
struct {
long long valid; /* - valid response */
long long update; /* - valid response used to update server's IP */
long long cname; /* - CNAME response requiring new resolution */
long long cname_error; /* - error when resolving CNAMEs */
long long any_err; /* - void response (usually because ANY qtype) */
long long nx; /* - NX response */
long long timeout; /* - queries which reached timeout */
long long refused; /* - queries refused */
long long other; /* - other type of response */
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 */;
} resolver;
} app; /* application specific counteurs */
};
#endif /* _HAPROXY_DNS_T_H */