--- /dev/null
+/*
+ * include/types/dns.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 _TYPES_DNS_H
+#define _TYPES_DNS_H
+
+/*DNS maximum values */
+/*
+ * Maximum issued from RFC:
+ * RFC 1035: https://www.ietf.org/rfc/rfc1035.txt chapter 2.3.4
+ * RFC 2671: http://tools.ietf.org/html/rfc2671
+ */
+#define DNS_MAX_LABEL_SIZE 63
+#define DNS_MAX_NAME_SIZE 255
+#define DNS_MAX_UDP_MESSAGE 4096
+
+/* DNS error messages */
+#define DNS_TOO_LONG_FQDN "hostname too long"
+#define DNS_LABEL_TOO_LONG "one label too long"
+#define DNS_INVALID_CHARACTER "found an invalid character"
+
+/* dns query class */
+#define DNS_RCLASS_IN 1 /* internet class */
+
+/* dns record types (non exhaustive list) */
+#define DNS_RTYPE_A 1 /* IPv4 address */
+#define DNS_RTYPE_CNAME 5 /* canonical name */
+#define DNS_RTYPE_AAAA 28 /* IPv6 address */
+#define DNS_RTYPE_ANY 255 /* all records */
+
+/* dns rcode values */
+#define DNS_RCODE_NO_ERROR 0 /* no error */
+#define DNS_RCODE_NX_DOMAIN 3 /* non existent domain */
+#define DNS_RCODE_REFUSED 5 /* query refused */
+
+/* DNS request or response header structure */
+struct dns_header {
+ unsigned short id:16; /* identifier */
+ unsigned char rd :1; /* recursion desired 0: no, 1: yes */
+ unsigned char tc :1; /* truncation 0:no, 1: yes */
+ unsigned char aa :1; /* authoritative answer 0: no, 1: yes */
+ unsigned char opcode :4; /* operation code */
+ unsigned char qr :1; /* query/response 0: query, 1: response */
+ unsigned char rcode :4; /* response code */
+ unsigned char z :1; /* no used */
+ unsigned char ad :1; /* authentic data */
+ unsigned char cd :1; /* checking disabled */
+ unsigned char ra :1; /* recursion available 0: no, 1: yes */
+ unsigned short qdcount :16; /* question count */
+ unsigned short ancount :16; /* answer count */
+ unsigned short nscount :16; /* authority count */
+ unsigned short arcount :16; /* additional count */
+};
+
+/* short structure to describe a DNS question */
+struct dns_question {
+ unsigned short qtype; /* question type */
+ unsigned short qclass; /* query class */
+};
+
+/*
+ * resolvers section and parameters. It is linked to the name servers
+ * servers points to it.
+ * current resolution are stored in a FIFO list.
+ */
+struct dns_resolvers {
+ struct list list; /* resolvers list */
+ char *id; /* resolvers unique identifier */
+ struct {
+ const char *file; /* file where the section appears */
+ int line; /* line where the section appears */
+ } conf; /* config information */
+ struct list nameserver_list; /* dns server list */
+ int count_nameservers; /* total number of nameservers in a resolvers section */
+ int resolve_retries; /* number of retries before giving up */
+ struct { /* time to: */
+ int retry; /* wait for a response before retrying */
+ } timeout;
+ struct { /* time to hold current data when */
+ int valid; /* a response is valid */
+ } hold;
+ struct task *t; /* timeout management */
+ struct list curr_resolution; /* current running resolutions */
+ struct eb_root query_ids; /* tree to quickly lookup/retrieve query ids currently in use */
+ /* used by each nameserver, but stored in resolvers since there must */
+ /* be a unique relation between an eb_root and an eb_node (resolution) */
+};
+
+/*
+ * structure describing a name server used during name resolution.
+ * A name server belongs to a resolvers section.
+ */
+struct dns_nameserver {
+ struct list list; /* nameserver chained list */
+ char *id; /* nameserver unique identifier */
+ struct {
+ const char *file; /* file where the section appears */
+ int line; /* line where the section appears */
+ } conf; /* config information */
+ struct dns_resolvers *resolvers;
+ struct dgram_conn *dgram; /* transport layer */
+ struct sockaddr_storage addr; /* IP address */
+ struct { /* numbers relted to this name server: */
+ long int sent; /* - queries sent */
+ long int valid; /* - valid response */
+ long int update; /* - valid response used to update server's IP */
+ long int cname; /* - CNAME response requiring new resolution */
+ long int cname_error; /* - error when resolving CNAMEs */
+ long int any_err; /* - void response (usually because ANY qtype) */
+ long int nx; /* - NX response */
+ long int timeout; /* - queries which reached timeout */
+ long int refused; /* - queries refused */
+ long int other; /* - other type of response */
+ long int invalid; /* - malformed DNS response */
+ long int too_big; /* - too big response */
+ long int outdated; /* - outdated response (server slower than the other ones) */
+ } counters;
+};
+
+/*
+ * resolution structure associated to single server and used to manage name resolution for
+ * this server.
+ * The only link between the resolution and a nameserver is through the query_id.
+ */
+struct dns_resolution {
+ struct list list; /* resolution list */
+ struct dns_resolvers *resolvers; /* resolvers section associated to this resolution */
+ void *requester; /* owner of this name resolution */
+ int (*requester_cb)(struct dns_resolution *, struct dns_nameserver *, unsigned char *, int);
+ /* requester callback for valid response */
+ int (*requester_error_cb)(struct dns_resolution *, int);
+ /* requester callback, for error management */
+ char *hostname_dn; /* server hostname in domain name label format */
+ int hostname_dn_len; /* server domain name label len */
+ int resolver_family_priority; /* which IP family should the resolver use when both are returned */
+ time_t last_resolution; /* time of the lastest valid resolution */
+ time_t last_sent_packet; /* time of the latest DNS packet sent */
+ time_t last_status_change; /* time of the latest DNS resolution status change */
+ int query_id; /* DNS query ID dedicated for this resolution */
+ struct eb32_node qid; /* ebtree query id */
+ int query_type; /* query type to send. By default DNS_RTYPE_ANY */
+ int status; /* status of the resolution being processed RSLV_STATUS_* */
+ int step; /* */
+ int try; /* current resolution try */
+ int try_cname; /* number of CNAME requests sent */
+ int nb_responses; /* count number of responses received */
+};
+
+/* last resolution status code */
+enum {
+ RSLV_STATUS_NONE = 0, /* no resolution occured yet */
+ RSLV_STATUS_VALID, /* no error */
+ RSLV_STATUS_INVALID, /* invalid responses */
+ RSLV_STATUS_ERROR, /* error */
+ RSLV_STATUS_NX, /* NXDOMAIN */
+ RSLV_STATUS_REFUSED, /* server refused our query */
+ RSLV_STATUS_TIMEOUT, /* no response from DNS servers */
+ RSLV_STATUS_OTHER, /* other errors */
+};
+
+/* current resolution step */
+enum {
+ RSLV_STEP_NONE = 0, /* nothing happening currently */
+ RSLV_STEP_RUNNING, /* resolution is running */
+};
+
+/* return codes after analyzing a DNS response */
+enum {
+ DNS_RESP_VALID = 0, /* valid response */
+ DNS_RESP_INVALID, /* invalid response (various type of errors can trigger it) */
+ DNS_RESP_ERROR, /* DNS error code */
+ DNS_RESP_NX_DOMAIN, /* resolution unsuccessful */
+ DNS_RESP_REFUSED, /* DNS server refused to answer */
+ DNS_RESP_ANCOUNT_ZERO, /* no answers in the response */
+ DNS_RESP_WRONG_NAME, /* response does not match query name */
+ DNS_RESP_CNAME_ERROR, /* error when resolving a CNAME in an atomic response */
+ DNS_RESP_TIMEOUT, /* DNS server has not answered in time */
+};
+
+/* return codes after searching an IP in a DNS response buffer, using a family preference */
+enum {
+ DNS_UPD_NO = 1, /* provided IP was found and preference is matched
+ * OR provided IP found and preference is not matched, but no IP
+ * matching preference was found */
+ DNS_UPD_SRVIP_NOT_FOUND, /* provided IP not found
+ * OR provided IP found and preference is not match and an IP
+ * matching preference was found */
+ DNS_UPD_CNAME, /* CNAME without any IP provided in the response */
+ DNS_UPD_NAME_ERROR, /* name in the response did not match the query */
+};
+
+#endif /* _TYPES_DNS_H */
#include <types/obj_type.h>
#include <types/peers.h>
#include <types/mailers.h>
+#include <types/dns.h>
#include <proto/acl.h>
#include <proto/auth.h>
return err_code;
}
+/*
+ * Parse a <resolvers> section.
+ * Returns the error code, 0 if OK, or any combination of :
+ * - ERR_ABORT: must abort ASAP
+ * - ERR_FATAL: we can continue parsing but not start the service
+ * - ERR_WARN: a warning has been emitted
+ * - ERR_ALERT: an alert has been emitted
+ * Only the two first ones can stop processing, the two others are just
+ * indicators.
+ */
+int cfg_parse_resolvers(const char *file, int linenum, char **args, int kwm)
+{
+ static struct dns_resolvers *curr_resolvers = NULL;
+ struct dns_nameserver *newnameserver = NULL;
+ const char *err;
+ int err_code = 0;
+ char *errmsg = NULL;
+
+ if (strcmp(args[0], "resolvers") == 0) { /* new resolvers section */
+ if (!*args[1]) {
+ Alert("parsing [%s:%d] : missing name for resolvers section.\n", file, linenum);
+ err_code |= ERR_ALERT | ERR_ABORT;
+ goto out;
+ }
+
+ err = invalid_char(args[1]);
+ if (err) {
+ Alert("parsing [%s:%d] : character '%c' is not permitted in '%s' name '%s'.\n",
+ file, linenum, *err, args[0], args[1]);
+ err_code |= ERR_ALERT | ERR_ABORT;
+ goto out;
+ }
+
+ list_for_each_entry(curr_resolvers, &dns_resolvers, list) {
+ /* Error if two resolvers owns the same name */
+ if (strcmp(curr_resolvers->id, args[1]) == 0) {
+ Alert("Parsing [%s:%d]: resolvers '%s' has same name as another resolvers (declared at %s:%d).\n",
+ file, linenum, args[1], curr_resolvers->conf.file, curr_resolvers->conf.line);
+ err_code |= ERR_ALERT | ERR_ABORT;
+ }
+ }
+
+ if ((curr_resolvers = (struct dns_resolvers *)calloc(1, sizeof(struct dns_resolvers))) == NULL) {
+ Alert("parsing [%s:%d] : out of memory.\n", file, linenum);
+ err_code |= ERR_ALERT | ERR_ABORT;
+ goto out;
+ }
+
+ /* default values */
+ LIST_ADDQ(&dns_resolvers, &curr_resolvers->list);
+ curr_resolvers->conf.file = strdup(file);
+ curr_resolvers->conf.line = linenum;
+ curr_resolvers->id = strdup(args[1]);
+ curr_resolvers->query_ids = EB_ROOT;
+ /* default hold period for valid is 10s */
+ curr_resolvers->hold.valid = 10;
+ curr_resolvers->timeout.retry = 1;
+ curr_resolvers->resolve_retries = 3;
+ LIST_INIT(&curr_resolvers->nameserver_list);
+ LIST_INIT(&curr_resolvers->curr_resolution);
+ }
+ else if (strcmp(args[0], "nameserver") == 0) { /* nameserver definition */
+ struct sockaddr_storage *sk;
+ int port1, port2;
+ struct protocol *proto;
+
+ if (!*args[2]) {
+ Alert("parsing [%s:%d] : '%s' expects <name> and <addr>[:<port>] as arguments.\n",
+ file, linenum, args[0]);
+ err_code |= ERR_ALERT | ERR_FATAL;
+ goto out;
+ }
+
+ err = invalid_char(args[1]);
+ if (err) {
+ Alert("parsing [%s:%d] : character '%c' is not permitted in server name '%s'.\n",
+ file, linenum, *err, args[1]);
+ err_code |= ERR_ALERT | ERR_FATAL;
+ goto out;
+ }
+
+ if ((newnameserver = (struct dns_nameserver *)calloc(1, sizeof(struct dns_nameserver))) == NULL) {
+ Alert("parsing [%s:%d] : out of memory.\n", file, linenum);
+ err_code |= ERR_ALERT | ERR_ABORT;
+ goto out;
+ }
+
+ /* the nameservers are linked backward first */
+ LIST_ADDQ(&curr_resolvers->nameserver_list, &newnameserver->list);
+ curr_resolvers->count_nameservers++;
+ newnameserver->resolvers = curr_resolvers;
+ newnameserver->conf.file = strdup(file);
+ newnameserver->conf.line = linenum;
+ newnameserver->id = strdup(args[1]);
+
+ sk = str2sa_range(args[2], &port1, &port2, &errmsg, NULL);
+ if (!sk) {
+ Alert("parsing [%s:%d] : '%s %s' : %s\n", file, linenum, args[0], args[1], errmsg);
+ err_code |= ERR_ALERT | ERR_FATAL;
+ goto out;
+ }
+
+ proto = protocol_by_family(sk->ss_family);
+ if (!proto || !proto->connect) {
+ Alert("parsing [%s:%d] : '%s %s' : connect() not supported for this address family.\n",
+ file, linenum, args[0], args[1]);
+ err_code |= ERR_ALERT | ERR_FATAL;
+ goto out;
+ }
+
+ if (port1 != port2) {
+ Alert("parsing [%s:%d] : '%s %s' : port ranges and offsets are not allowed in '%s'\n",
+ file, linenum, args[0], args[1], args[2]);
+ err_code |= ERR_ALERT | ERR_FATAL;
+ goto out;
+ }
+
+ newnameserver->addr = *sk;
+ }
+ else if (strcmp(args[0], "hold") == 0) { /* hold periods */
+ const char *res;
+ unsigned int time;
+
+ if (!*args[2]) {
+ Alert("parsing [%s:%d] : '%s' expects an <event> and a <time> as arguments.\n",
+ file, linenum, args[0]);
+ Alert("<event> can be either 'valid', 'nx', 'refused', 'timeout', or 'other'\n");
+ err_code |= ERR_ALERT | ERR_FATAL;
+ goto out;
+ }
+ res = parse_time_err(args[2], &time, TIME_UNIT_MS);
+ if (res) {
+ Alert("parsing [%s:%d]: unexpected character '%c' in argument to <%s>.\n",
+ file, linenum, *res, args[0]);
+ err_code |= ERR_ALERT | ERR_FATAL;
+ goto out;
+ }
+ if (strcmp(args[1], "valid") == 0)
+ curr_resolvers->hold.valid = time;
+ else {
+ Alert("parsing [%s:%d] : '%s' unknown <event>: '%s', expects 'valid'\n",
+ file, linenum, args[0], args[1]);
+ err_code |= ERR_ALERT | ERR_FATAL;
+ goto out;
+ }
+
+ }
+ else if (strcmp(args[0], "resolve_retries") == 0) {
+ if (!*args[1]) {
+ Alert("parsing [%s:%d] : '%s' expects <nb> as argument.\n",
+ file, linenum, args[0]);
+ err_code |= ERR_ALERT | ERR_FATAL;
+ goto out;
+ }
+ curr_resolvers->resolve_retries = atoi(args[1]);
+ }
+ else if (strcmp(args[0], "timeout") == 0) {
+ const char *res;
+ unsigned int timeout_retry;
+
+ if (!*args[2]) {
+ Alert("parsing [%s:%d] : '%s' expects 'retry' and <time> as arguments.\n",
+ file, linenum, args[0]);
+ err_code |= ERR_ALERT | ERR_FATAL;
+ goto out;
+ }
+ res = parse_time_err(args[2], &timeout_retry, TIME_UNIT_MS);
+ if (res) {
+ Alert("parsing [%s:%d]: unexpected character '%c' in argument to <%s>.\n",
+ file, linenum, *res, args[0]);
+ err_code |= ERR_ALERT | ERR_FATAL;
+ goto out;
+ }
+ curr_resolvers->timeout.retry = timeout_retry;
+ } /* neither "nameserver" nor "resolvers" */
+ else if (*args[0] != 0) {
+ Alert("parsing [%s:%d] : unknown keyword '%s' in '%s' section\n", file, linenum, args[0], cursection);
+ err_code |= ERR_ALERT | ERR_FATAL;
+ goto out;
+ }
+
+ out:
+ free(errmsg);
+ return err_code;
+}
/*
* Parse a line in a <listen>, <frontend> or <backend> section.
!cfg_register_section("userlist", cfg_parse_users) ||
!cfg_register_section("peers", cfg_parse_peers) ||
!cfg_register_section("mailers", cfg_parse_mailers) ||
- !cfg_register_section("namespace_list", cfg_parse_netns))
+ !cfg_register_section("namespace_list", cfg_parse_netns) ||
+ !cfg_register_section("resolvers", cfg_parse_resolvers))
return -1;
if ((f=fopen(file,"r")) == NULL)
--- /dev/null
+/*
+ * Name server resolution
+ *
+ * Copyright 2014 Baptiste Assmann <bedis9@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/types.h>
+
+#include <common/time.h>
+#include <common/ticks.h>
+
+#include <types/global.h>
+#include <types/dns.h>
+#include <types/proto_udp.h>
+
+#include <proto/checks.h>
+#include <proto/dns.h>
+#include <proto/fd.h>
+#include <proto/log.h>
+#include <proto/server.h>
+#include <proto/task.h>
+#include <proto/proto_udp.h>
+
+struct list dns_resolvers = LIST_HEAD_INIT(dns_resolvers);
+struct dns_resolution *resolution = NULL;
+
+static int64_t dns_query_id_seed; /* random seed */
+
+/* proto_udp callback functions for a DNS resolution */
+struct dgram_data_cb resolve_dgram_cb = {
+ .recv = dns_resolve_recv,
+ .send = dns_resolve_send,
+};
+
+#if DEBUG
+/*
+ * go through the resolutions associated to a resolvers section and print the ID and hostname in
+ * domain name format
+ * should be used for debug purpose only
+ */
+void dns_print_current_resolutions(struct dns_resolvers *resolvers)
+{
+ list_for_each_entry(resolution, &resolvers->curr_resolution, list) {
+ printf(" resolution %d for %s\n", resolution->query_id, resolution->hostname_dn);
+ }
+}
+#endif
+
+/*
+ * check if there is more than 1 resolution in the resolver's resolution list
+ * return value:
+ * 0: empty list
+ * 1: exactly one entry in the list
+ * 2: more than one entry in the list
+ */
+int dns_check_resolution_queue(struct dns_resolvers *resolvers)
+{
+
+ if (LIST_ISEMPTY(&resolvers->curr_resolution))
+ return 0;
+
+ if ((resolvers->curr_resolution.n) && (resolvers->curr_resolution.n == resolvers->curr_resolution.p))
+ return 1;
+
+ if (! ((resolvers->curr_resolution.n == resolvers->curr_resolution.p)
+ && (&resolvers->curr_resolution != resolvers->curr_resolution.n)))
+ return 2;
+
+ return 0;
+}
+
+/*
+ * reset all parameters of a DNS resolution to 0 (or equivalent)
+ * and clean it up from all associated lists (resolution->qid and resolution->list)
+ */
+void dns_reset_resolution(struct dns_resolution *resolution)
+{
+ /* update resolution status */
+ resolution->step = RSLV_STEP_NONE;
+
+ resolution->try = 0;
+ resolution->try_cname = 0;
+ resolution->last_resolution = now_ms;
+ resolution->nb_responses = 0;
+
+ /* clean up query id */
+ eb32_delete(&resolution->qid);
+ resolution->query_id = 0;
+ resolution->qid.key = 0;
+
+ /* default values */
+ resolution->query_type = DNS_RTYPE_ANY;
+
+ /* the second resolution in the queue becomes the first one */
+ LIST_DEL(&resolution->list);
+}
+
+/*
+ * function called when a network IO is generated on a name server socket for an incoming packet
+ * It performs the following actions:
+ * - check if the packet requires processing (not outdated resolution)
+ * - ensure the DNS packet received is valid and call requester's callback
+ * - call requester's error callback if invalid response
+ */
+void dns_resolve_recv(struct dgram_conn *dgram)
+{
+ struct dns_nameserver *nameserver;
+ struct dns_resolvers *resolvers;
+ struct dns_resolution *resolution;
+ unsigned char buf[DNS_MAX_UDP_MESSAGE + 1];
+ unsigned char *bufend;
+ int fd, buflen, ret;
+ unsigned short query_id;
+ struct eb32_node *eb;
+
+ fd = dgram->t.sock.fd;
+
+ /* check if ready for reading */
+ if (!fd_recv_ready(fd))
+ return;
+
+ /* no need to go further if we can't retrieve the nameserver */
+ if ((nameserver = (struct dns_nameserver *)dgram->owner) == NULL)
+ return;
+
+ resolvers = nameserver->resolvers;
+
+ /* process all pending input messages */
+ while (1) {
+ /* read message received */
+ memset(buf, '\0', DNS_MAX_UDP_MESSAGE + 1);
+ if ((buflen = recv(fd, (char*)buf , DNS_MAX_UDP_MESSAGE, 0)) < 0) {
+ /* FIXME : for now we consider EAGAIN only */
+ fd_cant_recv(fd);
+ break;
+ }
+
+ /* message too big */
+ if (buflen > DNS_MAX_UDP_MESSAGE) {
+ nameserver->counters.too_big += 1;
+ continue;
+ }
+
+ /* initializing variables */
+ bufend = buf + buflen; /* pointer to mark the end of the buffer */
+
+ /* read the query id from the packet (16 bits) */
+ if (buf + 2 > bufend) {
+ nameserver->counters.invalid += 1;
+ continue;
+ }
+ query_id = dns_response_get_query_id(buf);
+
+ /* search the query_id in the pending resolution tree */
+ if ((eb = eb32_lookup(&resolvers->query_ids, query_id)) == NULL) {
+ /* unknown query id means an outdated response and can be safely ignored */
+ nameserver->counters.outdated += 1;
+ continue;
+ }
+
+ /* known query id means a resolution in prgress */
+ resolution = eb32_entry(eb, struct dns_resolution, qid);
+
+ if (!resolution) {
+ nameserver->counters.outdated += 1;
+ continue;
+ }
+
+ /* number of responses received */
+ resolution->nb_responses += 1;
+
+ ret = dns_validate_dns_response(buf, bufend, resolution->hostname_dn, resolution->hostname_dn_len);
+
+ /* treat only errors */
+ switch (ret) {
+ case DNS_RESP_INVALID:
+ case DNS_RESP_WRONG_NAME:
+ nameserver->counters.invalid += 1;
+ resolution->requester_error_cb(resolution, DNS_RESP_INVALID);
+ continue;
+
+ case DNS_RESP_ERROR:
+ nameserver->counters.other += 1;
+ resolution->requester_error_cb(resolution, DNS_RESP_ERROR);
+ continue;
+
+ case DNS_RESP_ANCOUNT_ZERO:
+ nameserver->counters.any_err += 1;
+ resolution->requester_error_cb(resolution, DNS_RESP_ANCOUNT_ZERO);
+ continue;
+
+ case DNS_RESP_NX_DOMAIN:
+ nameserver->counters.nx += 1;
+ resolution->requester_error_cb(resolution, DNS_RESP_NX_DOMAIN);
+ continue;
+
+ case DNS_RESP_REFUSED:
+ nameserver->counters.refused += 1;
+ resolution->requester_error_cb(resolution, DNS_RESP_REFUSED);
+ continue;
+
+ case DNS_RESP_CNAME_ERROR:
+ nameserver->counters.cname_error += 1;
+ resolution->requester_error_cb(resolution, DNS_RESP_CNAME_ERROR);
+ continue;
+
+ }
+
+ resolution->requester_cb(resolution, nameserver, buf, buflen);
+ }
+}
+
+/*
+ * function called when a resolvers network socket is ready to send data
+ * It performs the following actions:
+ */
+void dns_resolve_send(struct dgram_conn *dgram)
+{
+ int fd;
+ struct dns_nameserver *nameserver;
+ struct dns_resolvers *resolvers;
+ struct dns_resolution *resolution;
+
+ fd = dgram->t.sock.fd;
+
+ /* check if ready for sending */
+ if (!fd_send_ready(fd))
+ return;
+
+ /* we don't want/need to be waked up any more for sending */
+ fd_stop_send(fd);
+
+ /* no need to go further if we can't retrieve the nameserver */
+ if ((nameserver = (struct dns_nameserver *)dgram->owner) == NULL)
+ return;
+
+ resolvers = nameserver->resolvers;
+ resolution = LIST_NEXT(&resolvers->curr_resolution, struct dns_resolution *, list);
+
+ dns_send_query(resolution);
+ dns_update_resolvers_timeout(resolvers);
+}
+
+/*
+ * forge and send a DNS query to resolvers associated to a resolution
+ * It performs the following actions:
+ * returns:
+ * 0 in case of error or safe ignorance
+ * 1 if no error
+ */
+int dns_send_query(struct dns_resolution *resolution)
+{
+ struct dns_resolvers *resolvers;
+ struct dns_nameserver *nameserver;
+ int ret, send_error, bufsize, fd;
+
+ resolvers = resolution->resolvers;
+
+ ret = send_error = 0;
+ bufsize = dns_build_query(resolution->query_id, resolution->query_type, resolution->hostname_dn,
+ resolution->hostname_dn_len, trash.str, trash.size);
+
+ if (bufsize == -1)
+ return 0;
+
+ list_for_each_entry(nameserver, &resolvers->nameserver_list, list) {
+ fd = nameserver->dgram->t.sock.fd;
+ errno = 0;
+
+ ret = send(fd, trash.str, bufsize, 0);
+
+ if (ret > 0)
+ nameserver->counters.sent += 1;
+
+ if (ret == 0 || errno == EAGAIN) {
+ /* nothing written, let's update the poller that we wanted to send
+ * but we were not able to */
+ fd_want_send(fd);
+ fd_cant_send(fd);
+ }
+ }
+
+ /* update resolution */
+ resolution->try += 1;
+ resolution->nb_responses = 0;
+ resolution->last_sent_packet = now_ms;
+
+ return 1;
+}
+
+/*
+ * update a resolvers' task timeout for next wake up
+ */
+void dns_update_resolvers_timeout(struct dns_resolvers *resolvers)
+{
+ struct dns_resolution *resolution;
+
+ if (LIST_ISEMPTY(&resolvers->curr_resolution)) {
+ /* no more resolution pending, so no wakeup anymore */
+ resolvers->t->expire = TICK_ETERNITY;
+ }
+ else {
+ resolution = LIST_NEXT(&resolvers->curr_resolution, struct dns_resolution *, list);
+ resolvers->t->expire = tick_add(resolution->last_sent_packet, resolvers->timeout.retry);
+ }
+}
+
+/*
+ * Function to validate that the buffer DNS response provided in <resp> and
+ * finishing before <bufend> is valid from a DNS protocol point of view.
+ * The caller can also ask the function to check if the response contains data
+ * for a domain name <dn_name> whose length is <dn_name_len> returns one of the
+ * DNS_RESP_* code.
+ */
+int dns_validate_dns_response(unsigned char *resp, unsigned char *bufend, char *dn_name, int dn_name_len)
+{
+ unsigned char *reader, *cname, *ptr;
+ int i, len, type, ancount, cnamelen;
+
+ reader = resp;
+ cname = NULL;
+ cnamelen = 0;
+ len = 0;
+
+ /* move forward 2 bytes for the query id */
+ reader += 2;
+ if (reader >= bufend)
+ return DNS_RESP_INVALID;
+
+ /*
+ * analyzing flags
+ * 1st byte can be ignored for now
+ * rcode is at the beginning of the second byte
+ */
+ reader += 1;
+ if (reader >= bufend)
+ return DNS_RESP_INVALID;
+
+ /*
+ * rcode is 4 latest bits
+ * ignore response if it contains an error
+ */
+ if ((*reader & 0x0f) != DNS_RCODE_NO_ERROR) {
+ if ((*reader & 0x0f) == DNS_RCODE_NX_DOMAIN)
+ return DNS_RESP_NX_DOMAIN;
+ else if ((*reader & 0x0f) == DNS_RCODE_REFUSED)
+ return DNS_RESP_REFUSED;
+
+ return DNS_RESP_ERROR;
+ }
+
+ /* move forward 1 byte for rcode */
+ reader += 1;
+ if (reader >= bufend)
+ return DNS_RESP_INVALID;
+
+ /* move forward 2 bytes for question count */
+ reader += 2;
+ if (reader >= bufend)
+ return DNS_RESP_INVALID;
+
+ /* analyzing answer count */
+ if (reader + 2 > bufend)
+ return DNS_RESP_INVALID;
+ ancount = reader[0] * 256 + reader[1];
+
+ if (ancount == 0)
+ return DNS_RESP_ANCOUNT_ZERO;
+
+ /* move forward 2 bytes for answer count */
+ reader += 2;
+ if (reader >= bufend)
+ return DNS_RESP_INVALID;
+
+ /* move forward 4 bytes authority and additional count */
+ reader += 4;
+ if (reader >= bufend)
+ return DNS_RESP_INVALID;
+
+ /* check if the name can stand in response */
+ if (dn_name && ((reader + dn_name_len + 1) > bufend))
+ return DNS_RESP_INVALID;
+
+ /* check hostname */
+ if (dn_name && (memcmp(reader, dn_name, dn_name_len) != 0))
+ return DNS_RESP_WRONG_NAME;
+
+ /* move forward hostname len bytes + 1 for NULL byte */
+ if (dn_name) {
+ reader = reader + dn_name_len + 1;
+ }
+ else {
+ ptr = reader;
+ while (*ptr) {
+ ptr++;
+ if (ptr >= bufend)
+ return DNS_RESP_INVALID;
+ }
+ reader = ptr + 1;
+ }
+
+ /* move forward 4 bytes for question type and question class */
+ reader += 4;
+ if (reader >= bufend)
+ return DNS_RESP_INVALID;
+
+ /* now parsing response records */
+ for (i = 1; i <= ancount; i++) {
+ if (reader >= bufend)
+ return DNS_RESP_INVALID;
+
+ /*
+ * name can be a pointer, so move forward reader cursor accordingly
+ * if 1st byte is '11XXXXXX', it means name is a pointer
+ * and 2nd byte gives the offset from resp where the hostname can
+ * be found
+ */
+ if ((*reader & 0xc0) == 0xc0) {
+ /*
+ * pointer, hostname can be found at resp + *(reader + 1)
+ */
+ if (reader + 1 > bufend)
+ return DNS_RESP_INVALID;
+
+ ptr = resp + *(reader + 1);
+
+ /* check if the pointer points inside the buffer */
+ if (ptr >= bufend)
+ return DNS_RESP_INVALID;
+ }
+ else {
+ /*
+ * name is a string which starts at first byte
+ * checking against last cname when recursing through the response
+ */
+ /* look for the end of the string and ensure it's in the buffer */
+ ptr = reader;
+ len = 0;
+ while (*ptr) {
+ ++len;
+ ++ptr;
+ if (ptr >= bufend)
+ return DNS_RESP_INVALID;
+ }
+
+ /* if cname is set, it means a CNAME recursion is in progress */
+ ptr = reader;
+ }
+
+ /* ptr now points to the name */
+ /* if cname is set, it means a CNAME recursion is in progress */
+ if (cname) {
+ /* check if the name can stand in response */
+ if ((reader + cnamelen) > bufend)
+ return DNS_RESP_INVALID;
+ /* compare cname and current name */
+ if (memcmp(ptr, cname, cnamelen) != 0)
+ return DNS_RESP_CNAME_ERROR;
+ }
+ /* compare server hostname to current name */
+ else if (dn_name) {
+ /* check if the name can stand in response */
+ if ((reader + dn_name_len) > bufend)
+ return DNS_RESP_INVALID;
+ if (memcmp(ptr, dn_name, dn_name_len) != 0)
+ return DNS_RESP_WRONG_NAME;
+ }
+
+ if ((*reader & 0xc0) == 0xc0) {
+ /* move forward 2 bytes for information pointer and address pointer */
+ reader += 2;
+ }
+ else {
+ if (cname) {
+ cname = reader;
+ cnamelen = dns_str_to_dn_label_len((const char *)cname);
+
+ /* move forward cnamelen bytes + NULL byte */
+ reader += (cnamelen + 1);
+ }
+ else {
+ reader += (len + 1);
+ }
+ }
+ if (reader >= bufend)
+ return DNS_RESP_INVALID;
+
+ /*
+ * we know the record is either for our server hostname
+ * or a valid CNAME in a crecursion
+ */
+
+ /* now reading record type (A, AAAA, CNAME, etc...) */
+ if (reader + 2 > bufend)
+ return DNS_RESP_INVALID;
+ type = reader[0] * 256 + reader[1];
+
+ /* move forward 2 bytes for type (2) */
+ reader += 2;
+
+ /* move forward 6 bytes for class (2) and ttl (4) */
+ reader += 6;
+ if (reader >= bufend)
+ return DNS_RESP_INVALID;
+
+ /* now reading data len */
+ if (reader + 2 > bufend)
+ return DNS_RESP_INVALID;
+ len = reader[0] * 256 + reader[1];
+
+ /* move forward 2 bytes for data len */
+ reader += 2;
+
+ /* analyzing record content */
+ switch (type) {
+ case DNS_RTYPE_A:
+ /* ipv4 is stored on 4 bytes */
+ if (len != 4)
+ return DNS_RESP_INVALID;
+ break;
+
+ case DNS_RTYPE_CNAME:
+ cname = reader;
+ cnamelen = len;
+ break;
+
+ case DNS_RTYPE_AAAA:
+ /* ipv6 is stored on 16 bytes */
+ if (len != 16)
+ return DNS_RESP_INVALID;
+ break;
+ } /* switch (record type) */
+
+ /* move forward len for analyzing next record in the response */
+ reader += len;
+ } /* for i 0 to ancount */
+
+ return DNS_RESP_VALID;
+}
+
+/*
+ * search dn_name resolution in resp.
+ * If existing IP not found, return the first IP matching family_priority,
+ * otherwise, first ip found
+ * The following tasks are the responsibility of the caller:
+ * - resp contains an error free DNS response
+ * - the response matches the dn_name
+ * For both cases above, dns_validate_dns_response is required
+ * returns one of the DNS_UPD_* code
+ */
+int dns_get_ip_from_response(unsigned char *resp, unsigned char *resp_end,
+ char *dn_name, int dn_name_len, void *currentip, short currentip_sin_family,
+ int family_priority, void **newip, short *newip_sin_family)
+{
+ int i, ancount, cnamelen, type, data_len, currentip_found;
+ unsigned char *reader, *cname, *ptr, *newip4, *newip6;
+
+ cname = *newip = newip4 = newip6 = NULL;
+ cnamelen = currentip_found = 0;
+ *newip_sin_family = AF_UNSPEC;
+ ancount = (((struct dns_header *)resp)->ancount);
+ ancount = *(resp + 7);
+
+ /* bypass DNS response header */
+ reader = resp + sizeof(struct dns_header);
+
+ /* bypass DNS query section */
+ /* move forward hostname len bytes + 1 for NULL byte */
+ reader = reader + dn_name_len + 1;
+
+ /* move forward 4 bytes for question type and question class */
+ reader += 4;
+
+ /* now parsing response records */
+ for (i = 1; i <= ancount; i++) {
+ /*
+ * name can be a pointer, so move forward reader cursor accordingly
+ * if 1st byte is '11XXXXXX', it means name is a pointer
+ * and 2nd byte gives the offset from buf where the hostname can
+ * be found
+ */
+ if ((*reader & 0xc0) == 0xc0)
+ ptr = resp + *(reader + 1);
+ else
+ ptr = reader;
+
+ if (cname && memcmp(ptr, cname, cnamelen))
+ return DNS_UPD_NAME_ERROR;
+ else if (memcmp(ptr, dn_name, dn_name_len))
+ return DNS_UPD_NAME_ERROR;
+
+ if ((*reader & 0xc0) == 0xc0) {
+ /* move forward 2 bytes for information pointer and address pointer */
+ reader += 2;
+ }
+ else {
+ if (cname) {
+ cname = reader;
+ cnamelen = dns_str_to_dn_label_len((char *)cname);
+
+ /* move forward cnamelen bytes + NULL byte */
+ reader += (cnamelen + 1);
+ }
+ else {
+ /* move forward dn_name_len bytes + NULL byte */
+ reader += (dn_name_len + 1);
+ }
+ }
+
+ /*
+ * we know the record is either for our server hostname
+ * or a valid CNAME in a crecursion
+ */
+
+ /* now reading record type (A, AAAA, CNAME, etc...) */
+ type = reader[0] * 256 + reader[1];
+
+ /* move forward 2 bytes for type (2) */
+ reader += 2;
+
+ /* move forward 6 bytes for class (2) and ttl (4) */
+ reader += 6;
+
+ /* now reading data len */
+ data_len = reader[0] * 256 + reader[1];
+
+ /* move forward 2 bytes for data len */
+ reader += 2;
+
+ /* analyzing record content */
+ switch (type) {
+ case DNS_RTYPE_A:
+ /* check if current reccord's IP is the same as server one's */
+ if ((currentip_sin_family == AF_INET)
+ && (*(uint32_t *)reader == *(uint32_t *)currentip)) {
+ currentip_found = 1;
+ newip4 = reader;
+ /* we can stop now if server's family preference is IPv4
+ * and its current IP is found in the response list */
+ if (family_priority == AF_INET)
+ return DNS_UPD_NO; /* DNS_UPD matrix #1 */
+ }
+ else if (!newip4) {
+ newip4 = reader;
+ }
+
+ /* move forward data_len for analyzing next record in the response */
+ reader += data_len;
+ break;
+
+ case DNS_RTYPE_CNAME:
+ cname = reader;
+ cnamelen = data_len;
+
+ reader += data_len;
+ break;
+
+ case DNS_RTYPE_AAAA:
+ /* check if current record's IP is the same as server's one */
+ if ((currentip_sin_family == AF_INET6) && (memcmp(reader, currentip, 16) == 0)) {
+ currentip_found = 1;
+ newip6 = reader;
+ /* we can stop now if server's preference is IPv6 or is not
+ * set (which implies we prioritize IPv6 over IPv4 */
+ if (family_priority == AF_INET6)
+ return DNS_UPD_NO;
+ }
+ else if (!newip6) {
+ newip6 = reader;
+ }
+
+ /* move forward data_len for analyzing next record in the response */
+ reader += data_len;
+ break;
+
+ default:
+ /* not supported record type */
+ /* move forward data_len for analyzing next record in the response */
+ reader += data_len;
+ } /* switch (record type) */
+ } /* for i 0 to ancount */
+
+ /* only CNAMEs in the response, no IP found */
+ if (cname && !newip4 && !newip6) {
+ return DNS_UPD_CNAME;
+ }
+
+ /* case when the caller looks first for an IPv4 address */
+ if (family_priority == AF_INET) {
+ if (newip4) {
+ *newip = newip4;
+ *newip_sin_family = AF_INET;
+ if (currentip_found == 1)
+ return DNS_UPD_NO;
+ return DNS_UPD_SRVIP_NOT_FOUND;
+ }
+ else if (newip6) {
+ *newip = newip6;
+ *newip_sin_family = AF_INET6;
+ if (currentip_found == 1)
+ return DNS_UPD_NO;
+ return DNS_UPD_SRVIP_NOT_FOUND;
+ }
+ }
+ /* case when the caller looks first for an IPv6 address */
+ else if (family_priority == AF_INET6) {
+ if (newip6) {
+ *newip = newip6;
+ *newip_sin_family = AF_INET6;
+ if (currentip_found == 1)
+ return DNS_UPD_NO;
+ return DNS_UPD_SRVIP_NOT_FOUND;
+ }
+ else if (newip4) {
+ *newip = newip4;
+ *newip_sin_family = AF_INET;
+ if (currentip_found == 1)
+ return DNS_UPD_NO;
+ return DNS_UPD_SRVIP_NOT_FOUND;
+ }
+ }
+ /* case when the caller have no preference (we prefer IPv6) */
+ else if (family_priority == AF_UNSPEC) {
+ if (newip6) {
+ *newip = newip6;
+ *newip_sin_family = AF_INET6;
+ if (currentip_found == 1)
+ return DNS_UPD_NO;
+ return DNS_UPD_SRVIP_NOT_FOUND;
+ }
+ else if (newip4) {
+ *newip = newip4;
+ *newip_sin_family = AF_INET;
+ if (currentip_found == 1)
+ return DNS_UPD_NO;
+ return DNS_UPD_SRVIP_NOT_FOUND;
+ }
+ }
+
+ /* no reason why we should change the server's IP address */
+ return DNS_UPD_NO;
+}
+
+/*
+ * returns the query id contained in a DNS response
+ */
+int dns_response_get_query_id(unsigned char *resp)
+{
+ /* read the query id from the response */
+ return resp[0] * 256 + resp[1];
+}
+
+/*
+ * used during haproxy's init phase
+ * parses resolvers sections and initializes:
+ * - task (time events) for each resolvers section
+ * - the datagram layer (network IO events) for each nameserver
+ * returns:
+ * 0 in case of error
+ * 1 when no error
+ */
+int dns_init_resolvers(void)
+{
+ struct dns_resolvers *curr_resolvers;
+ struct dns_nameserver *curnameserver;
+ struct dgram_conn *dgram;
+ struct task *t;
+ int fd;
+
+ /* give a first random value to our dns query_id seed */
+ dns_query_id_seed = random();
+
+ /* run through the resolvers section list */
+ list_for_each_entry(curr_resolvers, &dns_resolvers, list) {
+ /* create the task associated to the resolvers section */
+ if ((t = task_new()) == NULL) {
+ Alert("Starting [%s] resolvers: out of memory.\n", curr_resolvers->id);
+ return 0;
+ }
+
+ /* update task's parameters */
+ t->process = dns_process_resolve;
+ t->context = curr_resolvers;
+ t->expire = TICK_ETERNITY;
+
+ curr_resolvers->t = t;
+
+ list_for_each_entry(curnameserver, &curr_resolvers->nameserver_list, list) {
+ if ((dgram = calloc(1, sizeof(struct dgram_conn))) == NULL) {
+ Alert("Starting [%s/%s] nameserver: out of memory.\n", curr_resolvers->id,
+ curnameserver->id);
+ return 0;
+ }
+ /* update datagram's parameters */
+ dgram->owner = (void *)curnameserver;
+ dgram->data = &resolve_dgram_cb;
+
+ /* create network UDP socket for this nameserver */
+ if ((fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1) {
+ Alert("Starting [%s/%s] nameserver: can't create socket.\n", curr_resolvers->id,
+ curnameserver->id);
+ free(dgram);
+ dgram = NULL;
+ return 0;
+ }
+
+ /* "connect" the UDP socket to the name server IP */
+ if (connect(fd, (struct sockaddr*)&curnameserver->addr, sizeof(curnameserver->addr)) == -1) {
+ Alert("Starting [%s/%s] nameserver: can't connect socket.\n", curr_resolvers->id,
+ curnameserver->id);
+ close(fd);
+ free(dgram);
+ dgram = NULL;
+ return 0;
+ }
+
+ /* make the socket non blocking */
+ fcntl(fd, F_SETFL, O_NONBLOCK);
+
+ /* add the fd in the fd list and update its parameters */
+ fd_insert(fd);
+ fdtab[fd].owner = dgram;
+ fdtab[fd].iocb = dgram_fd_handler;
+ fd_want_recv(fd);
+ dgram->t.sock.fd = fd;
+
+ /* update nameserver's datagram property */
+ curnameserver->dgram = dgram;
+
+ continue;
+ }
+
+ /* task can be queued */
+ task_queue(t);
+ }
+
+ return 1;
+}
+
+/*
+ * Forge a DNS query. It needs the following information from the caller:
+ * - <query_id>: the DNS query id corresponding to this query
+ * - <query_type>: DNS_RTYPE_* request DNS record type (A, AAAA, ANY, etc...)
+ * - <hostname_dn>: hostname in domain name format
+ * - <hostname_dn_len>: length of <hostname_dn>
+ * To store the query, the caller must pass a buffer <buf> and its size <bufsize>
+ *
+ * the DNS query is stored in <buf>
+ * returns:
+ * -1 if <buf> is too short
+ */
+int dns_build_query(int query_id, int query_type, char *hostname_dn, int hostname_dn_len, char *buf, int bufsize)
+{
+ struct dns_header *dns;
+ struct dns_question *qinfo;
+ char *ptr, *bufend;
+
+ memset(buf, '\0', bufsize);
+ ptr = buf;
+ bufend = buf + bufsize;
+
+ /* check if there is enough room for DNS headers */
+ if (ptr + sizeof(struct dns_header) >= bufend)
+ return -1;
+
+ /* set dns query headers */
+ dns = (struct dns_header *)ptr;
+ dns->id = (unsigned short) htons(query_id);
+ dns->qr = 0; /* query */
+ dns->opcode = 0;
+ dns->aa = 0;
+ dns->tc = 0;
+ dns->rd = 1; /* recursion desired */
+ dns->ra = 0;
+ dns->z = 0;
+ dns->rcode = 0;
+ dns->qdcount = htons(1); /* 1 question */
+ dns->ancount = 0;
+ dns->nscount = 0;
+ dns->arcount = 0;
+
+ /* move forward ptr */
+ ptr += sizeof(struct dns_header);
+
+ /* check if there is enough room for query hostname */
+ if ((ptr + hostname_dn_len) >= bufend)
+ return -1;
+
+ /* set up query hostname */
+ memcpy(ptr, hostname_dn, hostname_dn_len);
+ ptr[hostname_dn_len + 1] = '\0';
+
+ /* move forward ptr */
+ ptr += (hostname_dn_len + 1);
+
+ /* check if there is enough room for query hostname*/
+ if (ptr + sizeof(struct dns_question) >= bufend)
+ return -1;
+
+ /* set up query info (type and class) */
+ qinfo = (struct dns_question *)ptr;
+ qinfo->qtype = htons(query_type);
+ qinfo->qclass = htons(DNS_RCLASS_IN);
+
+ ptr += sizeof(struct dns_question);
+
+ return ptr - buf;
+}
+
+/*
+ * turn a string into domain name label:
+ * www.haproxy.org into 3www7haproxy3org
+ * if dn memory is pre-allocated, you must provide its size in dn_len
+ * if dn memory isn't allocated, dn_len must be set to 0.
+ * In the second case, memory will be allocated.
+ * in case of error, -1 is returned, otherwise, number of bytes copied in dn
+ */
+char *dns_str_to_dn_label(char *string, char *dn, int dn_len)
+{
+ char *c, *d;
+ int i, offset;
+
+ /* offset between string size and theorical dn size */
+ offset = 1;
+
+ /*
+ * first, get the size of the string turned into its domain name version
+ * This function also validates the string respect the RFC
+ */
+ if ((i = dns_str_to_dn_label_len(string)) == -1)
+ return NULL;
+
+ /* yes, so let's check there is enough memory */
+ if (dn_len < i + offset)
+ return NULL;
+
+ i = strlen(string) + offset;
+ memcpy(dn + offset, string, i);
+ dn[i + offset] = '\0';
+ /* avoid a '\0' at the beginning of dn string which may prevent the for loop
+ * below from working.
+ * Actually, this is the reason of the offset. */
+ dn[0] = '0';
+
+ for (c = dn; *c ; ++c) {
+ /* c points to the first '0' char or a dot, which we don't want to read */
+ d = c + offset;
+ i = 0;
+ while (*d != '.' && *d) {
+ i++;
+ d++;
+ }
+ *c = i;
+
+ c = d - 1; /* because of c++ of the for loop */
+ }
+
+ return dn;
+}
+
+/*
+ * compute and return the length of <string> it it were translated into domain name
+ * label:
+ * www.haproxy.org into 3www7haproxy3org would return 16
+ * NOTE: add +1 for '\0' when allocating memory ;)
+ */
+int dns_str_to_dn_label_len(const char *string)
+{
+ return strlen(string) + 1;
+}
+
+/*
+ * validates host name:
+ * - total size
+ * - each label size individually
+ * returns:
+ * 0 in case of error. If <err> is not NULL, an error message is stored there.
+ * 1 when no error. <err> is left unaffected.
+ */
+int dns_hostname_validation(const char *string, char **err)
+{
+ const char *c, *d;
+ int i;
+
+ if (strlen(string) > DNS_MAX_NAME_SIZE) {
+ if (err)
+ *err = DNS_TOO_LONG_FQDN;
+ return 0;
+ }
+
+ c = string;
+ while (*c) {
+ d = c;
+
+ i = 0;
+ while (*d != '.' && *d && i <= DNS_MAX_LABEL_SIZE) {
+ i++;
+ if (!((*d == '-') || (*d == '_') ||
+ ((*d >= 'a') && (*d <= 'z')) ||
+ ((*d >= 'A') && (*d <= 'Z')) ||
+ ((*d >= '0') && (*d <= '9')))) {
+ if (err)
+ *err = DNS_INVALID_CHARACTER;
+ return 0;
+ }
+ d++;
+ }
+
+ if ((i >= DNS_MAX_LABEL_SIZE) && (d[i] != '.')) {
+ if (err)
+ *err = DNS_LABEL_TOO_LONG;
+ return 0;
+ }
+
+ if (*d == '\0')
+ goto out;
+
+ c = ++d;
+ }
+ out:
+ return 1;
+}
+
+/*
+ * 2 bytes random generator to generate DNS query ID
+ */
+uint16_t dns_rnd16(void)
+{
+ dns_query_id_seed ^= dns_query_id_seed << 13;
+ dns_query_id_seed ^= dns_query_id_seed >> 7;
+ dns_query_id_seed ^= dns_query_id_seed << 17;
+ return dns_query_id_seed;
+}
+
+
+/*
+ * function called when a timeout occurs during name resolution process
+ * if max number of tries is reached, then stop, otherwise, retry.
+ */
+struct task *dns_process_resolve(struct task *t)
+{
+ struct dns_resolvers *resolvers = t->context;
+ struct dns_resolution *resolution, *res_back;
+
+ /* timeout occurs inevitably for the first element of the FIFO queue */
+ if (LIST_ISEMPTY(&resolvers->curr_resolution)) {
+ /* no first entry, so wake up was useless */
+ t->expire = TICK_ETERNITY;
+ return t;
+ }
+
+ /* look for the first resolution which is not expired */
+ list_for_each_entry_safe(resolution, res_back, &resolvers->curr_resolution, list) {
+ /* when we find the first resolution in the future, then we can stop here */
+ if (tick_is_le(now_ms, resolution->last_sent_packet))
+ goto out;
+
+ /*
+ * if current resolution has been tried too many times and finishes in timeout
+ * we update its status and remove it from the list
+ */
+ if (resolution->try >= resolvers->resolve_retries) {
+ /* clean up resolution information and remove from the list */
+ dns_reset_resolution(resolution);
+
+ /* notify the result to the requester */
+ resolution->requester_error_cb(resolution, DNS_RESP_TIMEOUT);
+ }
+
+ /* check current resolution status */
+ if (resolution->step == RSLV_STEP_RUNNING) {
+ /* resend the DNS query */
+ dns_send_query(resolution);
+
+ /* check if we have more than one resolution in the list */
+ if (dns_check_resolution_queue(resolvers) > 1) {
+ /* move the rsolution to the end of the list */
+ LIST_DEL(&resolution->list);
+ LIST_ADDQ(&resolvers->curr_resolution, &resolution->list);
+ }
+ }
+ }
+
+ out:
+ dns_update_resolvers_timeout(resolvers);
+ return t;
+}