]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: dns: new DNS response parser
authorBaptiste Assmann <bedis9@gmail.com>
Sat, 14 May 2016 09:26:22 +0000 (11:26 +0200)
committerWilly Tarreau <w@1wt.eu>
Mon, 12 Sep 2016 17:54:23 +0000 (19:54 +0200)
New DNS response parser function which turn the DNS response from a
network buffer into a DNS structure, much easier for later analysis
by upper layer.

Memory is pre-allocated at start-up in a chunk dedicated to DNS
response store.

New error code to report a wrong number of queries in a DNS response.

include/proto/dns.h
include/proto/server.h
include/types/dns.h
src/dns.c
src/server.c

index 170eefa522c40ce191372bd899269e9918cd7fc2..c62834f94e464e3b75bbf37299229d27b03e2ab6 100644 (file)
@@ -32,8 +32,8 @@ int dns_build_query(int query_id, int query_type, char *hostname_dn, int hostnam
 struct task *dns_process_resolve(struct task *t);
 int dns_init_resolvers(void);
 uint16_t dns_rnd16(void);
-int dns_validate_dns_response(unsigned char *resp, unsigned char *bufend, char *dn_name, int dn_name_len);
-int dns_get_ip_from_response(unsigned char *resp, unsigned char *resp_end,
+int dns_validate_dns_response(unsigned char *resp, unsigned char *bufend, struct dns_response_packet *dns_p);
+int dns_get_ip_from_response(struct dns_response_packet *dns_p,
                              struct dns_resolution *resol, void *currentip,
                              short currentip_sin_family,
                              void **newip, short *newip_sin_family);
index 47630fe24c5390541166beb2e308bd517d651a3f..0ed68b8c1a0651dc3ec0f73e5c6a13a96952a50b 100644 (file)
@@ -48,7 +48,7 @@ void apply_server_state(void);
 
 /* functions related to server name resolution */
 int snr_update_srv_status(struct server *s);
-int snr_resolution_cb(struct dns_resolution *resolution, struct dns_nameserver *nameserver, unsigned char *response, int response_len);
+int snr_resolution_cb(struct dns_resolution *resolution, struct dns_nameserver *nameserver, struct dns_response_packet *dns_p);
 int snr_resolution_error_cb(struct dns_resolution *resolution, int error_code);
 
 /* increase the number of cumulated connections on the designated server */
index 03ffdc1483c16a317fc5673a57760bcb8256f5f9..5d6b5a1fbf4f283ae114bcf3163fe457609cc5e1 100644 (file)
 /* maximum number of answer record in a DNS response */
 #define DNS_MAX_ANSWER_RECORDS ((DNS_MAX_UDP_MESSAGE - DNS_HEADER_SIZE) / DNS_MIN_RECORD_SIZE)
 
+/* size of dns_buffer used to store responses from the buffer
+ * dns_buffer is used to store data collected from records found in a response.
+ * Before using it, caller will always check that there is at least DNS_MAX_NAME_SIZE bytes
+ * available */
+#define DNS_ANALYZE_BUFFER_SIZE DNS_MAX_UDP_MESSAGE + DNS_MAX_NAME_SIZE
+
 /* DNS error messages */
 #define DNS_TOO_LONG_FQDN      "hostname too long"
 #define DNS_LABEL_TOO_LONG     "one label too long"
@@ -204,7 +210,7 @@ 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);
+       int (*requester_cb)(struct dns_resolution *, struct dns_nameserver *, struct dns_response_packet *);
                                        /* requester callback for valid response */
        int (*requester_error_cb)(struct dns_resolution *, int);
                                        /* requester callback, for error management */
@@ -256,6 +262,7 @@ enum {
        DNS_RESP_TIMEOUT,               /* DNS server has not answered in time */
        DNS_RESP_TRUNCATED,             /* DNS response is truncated */
        DNS_RESP_NO_EXPECTED_RECORD,    /* No expected records were found in the response */
+       DNS_RESP_QUERY_COUNT_ERROR,     /* we did not get the expected number of queries in the response */
 };
 
 /* return codes after searching an IP in a DNS response buffer, using a family preference */
index b9dce6b1e32b3330c643ad71460ecf86b71ea00a..7d5ab002f1914d865c072b3340ce4413cbd91ccb 100644 (file)
--- a/src/dns.c
+++ b/src/dns.c
 struct list dns_resolvers = LIST_HEAD_INIT(dns_resolvers);
 struct dns_resolution *resolution = NULL;
 
+/*
+ * pre-allocated memory for maximum record names in a DNS response
+ * Each name is DNS_MAX_NAME_SIZE, we add 1 for the NULL character
+ *
+ * WARNING: this is not thread safe...
+ */
+struct dns_response_packet dns_response;
+struct chunk dns_trash = { };
+struct dns_query_item dns_query_records[DNS_MAX_QUERY_RECORDS];
+struct dns_answer_item dns_answer_records[DNS_MAX_ANSWER_RECORDS];
+
 static int64_t dns_query_id_seed;      /* random seed */
 
 /* proto_udp callback functions for a DNS resolution */
@@ -124,11 +135,13 @@ void dns_resolve_recv(struct dgram_conn *dgram)
        struct dns_nameserver *nameserver;
        struct dns_resolvers *resolvers;
        struct dns_resolution *resolution;
+       struct dns_query_item *query;
        unsigned char buf[DNS_MAX_UDP_MESSAGE + 1];
        unsigned char *bufend;
        int fd, buflen, ret;
        unsigned short query_id;
        struct eb32_node *eb;
+       struct dns_response_packet *dns_p = &dns_response;
 
        fd = dgram->t.sock.fd;
 
@@ -187,12 +200,12 @@ void dns_resolve_recv(struct dgram_conn *dgram)
                /* number of responses received */
                resolution->nb_responses += 1;
 
-               ret = dns_validate_dns_response(buf, bufend, resolution->hostname_dn, resolution->hostname_dn_len);
+               ret = dns_validate_dns_response(buf, bufend, dns_p);
 
                /* treat only errors */
                switch (ret) {
+               case DNS_RESP_QUERY_COUNT_ERROR:
                case DNS_RESP_INVALID:
-               case DNS_RESP_WRONG_NAME:
                        nameserver->counters.invalid += 1;
                        resolution->requester_error_cb(resolution, DNS_RESP_INVALID);
                        continue;
@@ -233,8 +246,18 @@ void dns_resolve_recv(struct dgram_conn *dgram)
                        continue;
                }
 
+               /* Now let's check the query's dname corresponds to the one we sent.
+                * We can check only the first query of the list. We send one query at a time
+                * so we get one query in the response */
+               query = LIST_NEXT(&dns_p->query_list, struct dns_query_item *, list);
+               if (query && memcmp(query->name, resolution->hostname_dn, resolution->hostname_dn_len) != 0) {
+                       nameserver->counters.other += 1;
+                       resolution->requester_error_cb(resolution, DNS_RESP_WRONG_NAME);
+                       continue;
+               }
+
                nameserver->counters.valid += 1;
-               resolution->requester_cb(resolution, nameserver, buf, buflen);
+               resolution->requester_cb(resolution, nameserver, dns_p);
        }
 }
 
@@ -331,36 +354,118 @@ void dns_update_resolvers_timeout(struct dns_resolvers *resolvers)
        }
 }
 
+/*
+ * Analyse, re-build and copy the name <name> from the DNS response packet <buffer>.
+ * <name> must point to the 'data_len' information or pointer 'c0' for compressed data.
+ * The result is copied into <dest>, ensuring we don't overflow using <dest_len>
+ * Returns the number of bytes the caller can move forward. If 0 it means an error occured
+ * while parsing the name.
+ * <offset> is the number of bytes the caller could move forward.
+ */
+int dns_read_name(unsigned char *buffer, unsigned char *bufend, unsigned char *name, char *destination, int dest_len, int *offset)
+{
+       int nb_bytes = 0, n = 0;
+       int label_len;
+       unsigned char *reader = name;
+       char *dest = destination;
+
+       while (1) {
+               /* name compression is in use */
+               if ((*reader & 0xc0) == 0xc0) {
+                       /* a pointer must point BEFORE current position */
+                       if ((buffer + reader[1]) > reader) {
+                               goto out_error;
+                       }
+
+                       n = dns_read_name(buffer, bufend, buffer + reader[1], dest, dest_len - nb_bytes, offset);
+                       if (n == 0)
+                               goto out_error;
+
+                       dest += n;
+                       nb_bytes += n;
+                       goto out;
+               }
+
+               label_len = *reader;
+               if (label_len == 0)
+                       goto out;
+               /* Check if:
+                *  - we won't read outside the buffer
+                *  - there is enough place in the destination
+                */
+               if ((reader + label_len >= bufend) || (nb_bytes + label_len >= dest_len))
+                       goto out_error;
+
+               /* +1 to take label len + label string */
+               label_len += 1;
+
+               memcpy(dest, reader, label_len);
+
+               dest += label_len;
+               nb_bytes += label_len;
+               reader += label_len;
+       }
+
+ out:
+       /* offset computation:
+        * parse from <name> until finding either NULL or a pointer "c0xx"
+        */
+       reader = name;
+       *offset = 0;
+       while (reader < bufend) {
+               if ((reader[0] & 0xc0) == 0xc0) {
+                       *offset += 2;
+                       break;
+               }
+               else if (*reader == 0) {
+                       *offset += 1;
+                       break;
+               }
+               *offset += 1;
+               ++reader;
+       }
+
+       return nb_bytes;
+
+ out_error:
+       return 0;
+}
+
 /*
  * 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.
+ *
+ * The result is stored in the structured pointed by <dns_p>.
+ * It's up to the caller to allocate memory for <dns_p>.
+ *
+ * This function returns one of the DNS_RESP_* code to indicate the type of
+ * error found.
  */
-int dns_validate_dns_response(unsigned char *resp, unsigned char *bufend, char *dn_name, int dn_name_len)
+int dns_validate_dns_response(unsigned char *resp, unsigned char *bufend, struct dns_response_packet *dns_p)
 {
-       unsigned char *reader, *cname, *ptr;
-       int i, len, flags, type, ancount, cnamelen, expected_record;
+       unsigned char *reader;
+       char *previous_dname, tmpname[DNS_MAX_NAME_SIZE];
+       int len, flags, offset, ret;
+       int dns_query_record_id, dns_answer_record_id;
+       struct dns_query_item *dns_query;
+       struct dns_answer_item *dns_answer_record;
 
        reader = resp;
-       cname = NULL;
-       cnamelen = 0;
        len = 0;
-       expected_record = 0; /* flag to report if at least one expected record type is found in the response.
-                             * For now, only records containing an IP address (A and AAAA) are
-                             * considered as expected.
-                             * Later, this function may be updated to let the caller decide what type
-                             * of record is expected to consider the response as valid. (SRV or TXT types)
-                             */
-
-       /* move forward 2 bytes for the query id */
-       reader += 2;
-       if (reader >= bufend)
+       previous_dname = NULL;
+
+       /* initialization of local buffer */
+       memset(dns_p, '\0', sizeof(struct dns_response_packet));
+       chunk_reset(&dns_trash);
+
+       /* query id */
+       if (reader + 2 >= bufend)
                return DNS_RESP_INVALID;
+       dns_p->header.id = reader[0] * 256 + reader[1];
+       reader += 2;
 
        /*
-        * flags are stored over 2 bytes
+        * flags and rcode are stored over 2 bytes
         * First byte contains:
         *  - response flag (1 bit)
         *  - opcode (4 bits)
@@ -387,196 +492,215 @@ int dns_validate_dns_response(unsigned char *resp, unsigned char *bufend, char *
 
        /* move forward 2 bytes for flags */
        reader += 2;
-       if (reader >= bufend)
-               return DNS_RESP_INVALID;
 
-       /* move forward 2 bytes for question count */
-       reader += 2;
-       if (reader >= bufend)
+       /* 2 bytes for question count */
+       if (reader + 2 >= bufend)
                return DNS_RESP_INVALID;
-
-       /* analyzing answer count */
-       if (reader + 2 > bufend)
+       dns_p->header.qdcount = reader[0] * 256 + reader[1];
+       /* (for now) we send one query only, so we expect only one in the response too */
+       if (dns_p->header.qdcount != 1)
+               return DNS_RESP_QUERY_COUNT_ERROR;
+       if (dns_p->header.qdcount > DNS_MAX_QUERY_RECORDS)
                return DNS_RESP_INVALID;
-       ancount = reader[0] * 256 + reader[1];
+       reader += 2;
 
-       if (ancount == 0)
+       /* 2 bytes for answer count */
+       if (reader + 2 >= bufend)
+               return DNS_RESP_INVALID;
+       dns_p->header.ancount = reader[0] * 256 + reader[1];
+       if (dns_p->header.ancount == 0)
                return DNS_RESP_ANCOUNT_ZERO;
-
-       /* move forward 2 bytes for answer count */
-       reader += 2;
-       if (reader >= bufend)
+       /* check if too many records are announced */
+       if (dns_p->header.ancount > DNS_MAX_ANSWER_RECORDS)
                return DNS_RESP_INVALID;
+       reader += 2;
 
-       /* move forward 4 bytes authority and additional count */
-       reader += 4;
-       if (reader >= bufend)
+       /* 2 bytes authority count */
+       if (reader + 2 >= bufend)
                return DNS_RESP_INVALID;
+       dns_p->header.nscount = reader[0] * 256 + reader[1];
+       reader += 2;
 
-       /* check if the name can stand in response */
-       if (dn_name && ((reader + dn_name_len + 1) > bufend))
+       /* 2 bytes additional count */
+       if (reader + 2 >= bufend)
                return DNS_RESP_INVALID;
+       dns_p->header.arcount = reader[0] * 256 + reader[1];
+       reader += 2;
 
-       /* check hostname */
-       if (dn_name && (memcmp(reader, dn_name, dn_name_len) != 0))
-               return DNS_RESP_WRONG_NAME;
+       /* parsing dns queries */
+       LIST_INIT(&dns_p->query_list);
+       for (dns_query_record_id = 0; dns_query_record_id < dns_p->header.qdcount; dns_query_record_id++) {
+               /* use next pre-allocated dns_query_item after ensuring there is
+                * still one available.
+                * It's then added to our packet query list.
+                */
+               if (dns_query_record_id > DNS_MAX_QUERY_RECORDS)
+                       return DNS_RESP_INVALID;
+               dns_query = &dns_query_records[dns_query_record_id];
+               LIST_ADDQ(&dns_p->query_list, &dns_query->list);
 
-       /* 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;
-       }
+               /* name is a NULL terminated string in our case, since we have
+                * one query per response and the first one can't be compressed
+                * (using the 0x0c format)
+                */
+               offset = 0;
+               len = dns_read_name(resp, bufend, reader, dns_query->name, DNS_MAX_NAME_SIZE, &offset);
 
-       /* move forward 4 bytes for question type and question class */
-       reader += 4;
-       if (reader >= bufend)
-               return DNS_RESP_INVALID;
+               if (len == 0)
+                       return DNS_RESP_INVALID;
+
+               reader += offset;
+               previous_dname = dns_query->name;
+
+               /* move forward 2 bytes for question type */
+               if (reader + 2 >= bufend)
+                       return DNS_RESP_INVALID;
+               dns_query->type = reader[0] * 256 + reader[1];
+               reader += 2;
+
+               /* move forward 2 bytes for question class */
+               if (reader + 2 >= bufend)
+                       return DNS_RESP_INVALID;
+               dns_query->class = reader[0] * 256 + reader[1];
+               reader += 2;
+       }
 
        /* now parsing response records */
-       for (i = 1; i <= ancount; i++) {
+       LIST_INIT(&dns_p->answer_list);
+       for (dns_answer_record_id = 0; dns_answer_record_id < dns_p->header.ancount; dns_answer_record_id++) {
                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;
+               /* pull next response record from the list, if still one available, then add it
+                * to the record list */
+               if (dns_answer_record_id > DNS_MAX_ANSWER_RECORDS)
+                       return DNS_RESP_INVALID;
+               dns_answer_record = &dns_answer_records[dns_answer_record_id];
+               LIST_ADDQ(&dns_p->answer_list, &dns_answer_record->list);
 
-                       ptr = resp + *(reader + 1);
+               offset = 0;
+               len = dns_read_name(resp, bufend, reader, tmpname, DNS_MAX_NAME_SIZE, &offset);
 
-                       /* check if the pointer points inside the buffer */
-                       if (ptr >= bufend)
+               if (len == 0)
+                       return DNS_RESP_INVALID;
+
+               /* check if the current record dname is valid.
+                * previous_dname points either to queried dname or last CNAME target
+                */
+               if (memcmp(previous_dname, tmpname, len) != 0) {
+                       if (dns_answer_record_id == 0) {
+                               /* first record, means a mismatch issue between queried dname
+                                * and dname found in the first record */
                                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;
+                       } else {
+                               /* if not the first record, this means we have a CNAME resolution
+                                * error */
+                               return DNS_RESP_CNAME_ERROR;
                        }
 
-                       /* if cname is set, it means a CNAME recursion is in progress */
-                       ptr = reader;
                }
 
-               /* ptr now points to the name */
-               if ((*reader & 0xc0) != 0xc0) {
-                       /* 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;
-
-                               cname = reader;
-                               cnamelen = dns_str_to_dn_label_len((const char *)cname);
-
-                               /* move forward cnamelen bytes + NULL byte */
-                               reader += (cnamelen + 1);
-                       }
-                       /* 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;
+               dns_answer_record->name = chunk_newstr(&dns_trash);
+               if (dns_answer_record->name == NULL)
+                       return DNS_RESP_INVALID;
 
-                               reader += (dn_name_len + 1);
-                       }
-                       else {
-                               reader += (len + 1);
-                       }
-               }
-               else {
-                       /* shortname in progress */
-                       /* move forward 2 bytes for information pointer and address pointer */
-                       reader += 2;
-               }
+               ret = chunk_strncat(&dns_trash, tmpname, len);
+               if (ret == 0)
+                       return DNS_RESP_INVALID;
 
+               reader += offset;
                if (reader >= bufend)
                        return DNS_RESP_INVALID;
 
-               /*
-                * we know the record is either for our server hostname
-                * or a valid CNAME in a crecursion
-                */
+               if (reader >= bufend)
+                       return DNS_RESP_INVALID;
 
-               /* now reading record type (A, AAAA, CNAME, etc...) */
+               /* 2 bytes for record type (A, AAAA, CNAME, etc...) */
                if (reader + 2 > bufend)
                        return DNS_RESP_INVALID;
-               type = reader[0] * 256 + reader[1];
+               dns_answer_record->type = reader[0] * 256 + reader[1];
+               reader += 2;
 
-               /* move forward 2 bytes for type (2) */
+               /* 2 bytes for class (2) */
+               if (reader + 2 > bufend)
+                       return DNS_RESP_INVALID;
+               dns_answer_record->class = reader[0] * 256 + reader[1];
                reader += 2;
 
-               /* move forward 6 bytes for class (2) and ttl (4) */
-               reader += 6;
-               if (reader >= bufend)
+               /* 4 bytes for ttl (4) */
+               if (reader + 4 > bufend)
                        return DNS_RESP_INVALID;
+               dns_answer_record->ttl =   reader[0] * 16777216 + reader[1] * 65536
+                                        + reader[2] * 256 + reader[3];
+               reader += 4;
 
                /* now reading data len */
                if (reader + 2 > bufend)
                        return DNS_RESP_INVALID;
-               len = reader[0] * 256 + reader[1];
+               dns_answer_record->data_len = reader[0] * 256 + reader[1];
 
                /* move forward 2 bytes for data len */
                reader += 2;
 
                /* analyzing record content */
-               switch (type) {
+               switch (dns_answer_record->type) {
                        case DNS_RTYPE_A:
                                /* ipv4 is stored on 4 bytes */
-                               if (len != 4)
+                               if (dns_answer_record->data_len != 4)
                                        return DNS_RESP_INVALID;
-                               expected_record = 1;
+                               dns_answer_record->address.sa_family = AF_INET;
+                               memcpy(&(((struct sockaddr_in *)&dns_answer_record->address)->sin_addr),
+                                               reader, dns_answer_record->data_len);
                                break;
 
                        case DNS_RTYPE_CNAME:
-                               cname = reader;
-                               cnamelen = len;
+                               /* check if this is the last record and update the caller about the status:
+                                * no IP could be found and last record was a CNAME. Could be triggered
+                                * by a wrong query type
+                                *
+                                * + 1 because dns_answer_record_id starts at 0 while number of answers
+                                * is an integer and starts at 1.
+                                */
+                               if (dns_answer_record_id + 1 == dns_p->header.ancount)
+                                       return DNS_RESP_CNAME_ERROR;
+
+                               offset = 0;
+                               len = dns_read_name(resp, bufend, reader, tmpname, DNS_MAX_NAME_SIZE, &offset);
+
+                               if (len == 0)
+                                       return DNS_RESP_INVALID;
+
+                               dns_answer_record->target = chunk_newstr(&dns_trash);
+                               if (dns_answer_record->target == NULL)
+                                       return DNS_RESP_INVALID;
+
+                               ret = chunk_strncat(&dns_trash, tmpname, len);
+                               if (ret == 0)
+                                       return DNS_RESP_INVALID;
+
+                               previous_dname = dns_answer_record->target;
+
                                break;
 
                        case DNS_RTYPE_AAAA:
                                /* ipv6 is stored on 16 bytes */
-                               if (len != 16)
+                               if (dns_answer_record->data_len != 16)
                                        return DNS_RESP_INVALID;
-                               expected_record = 1;
+                               dns_answer_record->address.sa_family = AF_INET6;
+                               memcpy(&(((struct sockaddr_in6 *)&dns_answer_record->address)->sin6_addr),
+                                               reader, dns_answer_record->data_len);
                                break;
+
                } /* switch (record type) */
 
-               /* move forward len for analyzing next record in the response */
-               reader += len;
+               /* move forward dns_answer_record->data_len for analyzing next record in the response */
+               reader += dns_answer_record->data_len;
        } /* for i 0 to ancount */
 
-       if (expected_record == 0)
-               return DNS_RESP_NO_EXPECTED_RECORD;
+       /* let's add a last \0 to close our last string */
+       ret = chunk_strncat(&dns_trash, "\0", 1);
+       if (ret == 0)
+               return DNS_RESP_INVALID;
 
        return DNS_RESP_VALID;
 }
@@ -592,18 +716,19 @@ int dns_validate_dns_response(unsigned char *resp, unsigned char *bufend, char *
  * returns one of the DNS_UPD_* code
  */
 #define DNS_MAX_IP_REC 20
-int dns_get_ip_from_response(unsigned char *resp, unsigned char *resp_end,
+int dns_get_ip_from_response(struct dns_response_packet *dns_p,
                              struct dns_resolution *resol, void *currentip,
                              short currentip_sin_family,
                              void **newip, short *newip_sin_family)
 {
+       struct dns_answer_item *record;
        int family_priority;
        char *dn_name;
        int dn_name_len;
-       int i, ancount, cnamelen, type, data_len, currentip_found;
-       unsigned char *reader, *cname, *ptr, *newip4, *newip6;
+       int i, cnamelen, currentip_found;
+       unsigned char *cname, *newip4, *newip6;
        struct {
-               unsigned char *ip;
+               void *ip;
                unsigned char type;
        } rec[DNS_MAX_IP_REC];
        int currentip_sel;
@@ -614,59 +739,19 @@ int dns_get_ip_from_response(unsigned char *resp, unsigned char *resp_end,
        family_priority = resol->opts->family_prio;
        dn_name = resol->hostname_dn;
        dn_name_len = resol->hostname_dn_len;
-
        cname = *newip = newip4 = newip6 = NULL;
        cnamelen = currentip_found = 0;
        *newip_sin_family = AF_UNSPEC;
-       ancount = *(resp + 7);  /* Assume no more than 256 answers */
-
-       /* 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;
-
+       list_for_each_entry(record, &dns_response.answer_list, list) {
                if (cname) {
-                       if (memcmp(ptr, cname, cnamelen)) {
+                       if (memcmp(record->name, cname, cnamelen) != 0) {
                                return DNS_UPD_NAME_ERROR;
                        }
                }
-               else if (memcmp(ptr, dn_name, dn_name_len))
+               else if (memcmp(record->name, dn_name, dn_name_len) != 0) {
                        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);
-                       }
                }
 
                /*
@@ -674,56 +759,32 @@ int dns_get_ip_from_response(unsigned char *resp, unsigned char *resp_end,
                 * 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) {
+               switch (record->type) {
                        case DNS_RTYPE_A:
                                /* Store IPv4, only if some room is avalaible. */
                                if (rec_nb < DNS_MAX_IP_REC) {
-                                       rec[rec_nb].ip = reader;
+                                       rec[rec_nb].ip = &(((struct sockaddr_in *)&record->address)->sin_addr);
                                        rec[rec_nb].type = AF_INET;
                                        rec_nb++;
                                }
-                               /* move forward data_len for analyzing next record in the response */
-                               reader += data_len;
                                break;
 
                        case DNS_RTYPE_CNAME:
-                               cname = reader;
-                               cnamelen = data_len;
+                               cname = record->target;
+                               cnamelen = record->data_len;
 
-                               reader += data_len;
                                break;
 
                        case DNS_RTYPE_AAAA:
                                /* Store IPv6, only if some room is avalaible. */
                                if (rec_nb < DNS_MAX_IP_REC) {
-                                       rec[rec_nb].ip = reader;
+                                       rec[rec_nb].ip = &(((struct sockaddr_in6 *)&record->address)->sin6_addr);
                                        rec[rec_nb].type = AF_INET6;
                                        rec_nb++;
                                }
-                               /* 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) */
        } /* list for each record entries */
 
@@ -886,8 +947,19 @@ int dns_init_resolvers(void)
        struct dns_nameserver *curnameserver;
        struct dgram_conn *dgram;
        struct task *t;
+       char *dns_trash_str;
        int fd;
 
+       dns_trash_str = malloc(global.tune.bufsize);
+       if (dns_trash_str == NULL) {
+               Alert("Starting [%s] resolvers: out of memory.\n", curr_resolvers->id);
+               return 0;
+       }
+
+       /* allocate memory for the dns_trash buffer used to temporarily store
+        * the records of the received response */
+       chunk_init(&dns_trash, dns_trash_str, global.tune.bufsize);
+
        /* give a first random value to our dns query_id seed */
        dns_query_id_seed = random();
 
index b105e28eb3aa07787738e5642e25b3d861d691a7..b6eef5db9ca9bad85822c9f2aa9574991da8698c 100644 (file)
@@ -2820,17 +2820,15 @@ int snr_update_srv_status(struct server *s)
  *  0 on error
  *  1 when no error or safe ignore
  */
-int snr_resolution_cb(struct dns_resolution *resolution, struct dns_nameserver *nameserver, unsigned char *response, int response_len)
+int snr_resolution_cb(struct dns_resolution *resolution, struct dns_nameserver *nameserver, struct dns_response_packet *dns_p)
 {
        struct server *s;
        void *serverip, *firstip;
        short server_sin_family, firstip_sin_family;
-       unsigned char *response_end;
        int ret;
        struct chunk *chk = get_trash_chunk();
 
        /* initializing variables */
-       response_end = response + response_len; /* pointer to mark the end of the response */
        firstip = NULL;         /* pointer to the first valid response found */
                                /* it will be used as the new IP if a change is required */
        firstip_sin_family = AF_UNSPEC;
@@ -2854,7 +2852,7 @@ int snr_resolution_cb(struct dns_resolution *resolution, struct dns_nameserver *
                        goto invalid;
        }
 
-       ret = dns_get_ip_from_response(response, response_end, resolution,
+       ret = dns_get_ip_from_response(dns_p, resolution,
                                       serverip, server_sin_family, &firstip,
                                       &firstip_sin_family);