DNS Record Write (#598)

The `ares_dns_record_t` data structure created in the prior release is capable of holding a complete parsed DNS message and also provides all helpers in order to fill in the data structure.  This PR adds write capabilities for this data structure to form a complete message and supports features such as DNS name compression as defined in RFC1035.  Though this message writing capability goes further than c-ares internally needs, external users may find it useful ... and we may find it useful for test validation as well.

This also replaces the existing message writing code in `ares_create_query()`, as well rewriting the request message without EDNS in ares_process.c's `process_answer()`.

Fix By: Brad House (@bradh352)
pull/600/head
Brad House 1 year ago committed by GitHub
parent 18396b3c25
commit 784ee5a754
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      src/lib/Makefile.inc
  2. 302
      src/lib/ares__buf.c
  3. 49
      src/lib/ares__buf.h
  4. 200
      src/lib/ares_create_query.c
  5. 21
      src/lib/ares_dns_mapping.c
  6. 675
      src/lib/ares_dns_name.c
  7. 6
      src/lib/ares_dns_parse.c
  8. 42
      src/lib/ares_dns_record.c
  9. 40
      src/lib/ares_dns_record.h
  10. 847
      src/lib/ares_dns_write.c
  11. 2
      src/lib/ares_expand_name.c
  12. 49
      src/lib/ares_private.h
  13. 132
      src/lib/ares_process.c
  14. 15
      src/lib/ares_send.c
  15. 2
      test/ares-test-internal.cc
  16. 6
      test/ares-test-misc.cc

@ -22,8 +22,10 @@ CSOURCES = ares__addrinfo2hostent.c \
ares_data.c \
ares_destroy.c \
ares_dns_mapping.c \
ares_dns_name.c \
ares_dns_parse.c \
ares_dns_record.c \
ares_dns_write.c \
ares_expand_name.c \
ares_expand_string.c \
ares_fds.c \

@ -45,27 +45,7 @@ struct ares__buf {
* SIZE_MAX if not set. */
};
/* Reserved characters for names that need to be escaped */
static ares_bool_t is_reservedch(int ch)
{
switch (ch) {
case '"':
case '.':
case ';':
case '\\':
case '(':
case ')':
case '@':
case '$':
return ARES_TRUE;
default:
break;
}
return ARES_FALSE;
}
static ares_bool_t ares__isprint(int ch)
ares_bool_t ares__isprint(int ch)
{
if (ch >= 0x20 && ch <= 0x7E) {
return ARES_TRUE;
@ -253,6 +233,20 @@ static ares_status_t ares__buf_ensure_space(ares__buf_t *buf,
return ARES_SUCCESS;
}
ares_status_t ares__buf_set_length(ares__buf_t *buf, size_t len)
{
if (buf == NULL || ares__buf_is_const(buf)) {
return ARES_EFORMERR;
}
if (len >= buf->alloc_buf_len - buf->offset) {
return ARES_EFORMERR;
}
buf->data_len = len;
return ARES_SUCCESS;
}
ares_status_t ares__buf_append(ares__buf_t *buf, const unsigned char *data,
size_t data_len)
{
@ -277,6 +271,50 @@ ares_status_t ares__buf_append_byte(ares__buf_t *buf, unsigned char byte)
return ares__buf_append(buf, &byte, 1);
}
ares_status_t ares__buf_append_be16(ares__buf_t *buf, unsigned short u16)
{
ares_status_t status;
status = ares__buf_append_byte(buf, (unsigned char)((u16 >> 8) & 0xff));
if (status != ARES_SUCCESS) {
return status;
}
status = ares__buf_append_byte(buf, (unsigned char)(u16 & 0xff));
if (status != ARES_SUCCESS) {
return status;
}
return ARES_SUCCESS;
}
ares_status_t ares__buf_append_be32(ares__buf_t *buf, unsigned int u32)
{
ares_status_t status;
status = ares__buf_append_byte(buf, ((unsigned char)(u32 >> 24) & 0xff));
if (status != ARES_SUCCESS) {
return status;
}
status = ares__buf_append_byte(buf, ((unsigned char)(u32 >> 16) & 0xff));
if (status != ARES_SUCCESS) {
return status;
}
status = ares__buf_append_byte(buf, ((unsigned char)(u32 >> 8) & 0xff));
if (status != ARES_SUCCESS) {
return status;
}
status = ares__buf_append_byte(buf, ((unsigned char)u32 & 0xff));
if (status != ARES_SUCCESS) {
return status;
}
return ARES_SUCCESS;
}
unsigned char *ares__buf_append_start(ares__buf_t *buf, size_t *len)
{
ares_status_t status;
@ -577,73 +615,6 @@ ares_status_t ares__buf_fetch_bytes_into_buf(ares__buf_t *buf,
return ares__buf_consume(buf, len);
}
static ares_status_t ares__buf_fetch_dnsname_into_buf(ares__buf_t *buf,
ares__buf_t *dest,
size_t len,
ares_bool_t is_hostname)
{
size_t remaining_len;
const unsigned char *ptr = ares__buf_fetch(buf, &remaining_len);
ares_status_t status;
size_t i;
if (buf == NULL || len == 0 || remaining_len < len) {
return ARES_EBADRESP;
}
for (i = 0; i < len; i++) {
unsigned char c = ptr[i];
/* Hostnames have a very specific allowed character set. Anything outside
* of that (non-printable and reserved included) are disallowed */
if (is_hostname && !ares__is_hostnamech(c)) {
status = ARES_EBADRESP;
goto fail;
}
/* NOTE: dest may be NULL if the user is trying to skip the name. validation
* still occurs above. */
if (dest == NULL) {
continue;
}
/* Non-printable characters need to be output as \DDD */
if (!ares__isprint(c)) {
unsigned char escape[4];
escape[0] = '\\';
escape[1] = '0' + (c / 100);
escape[2] = '0' + ((c % 100) / 10);
escape[3] = '0' + (c % 10);
status = ares__buf_append(dest, escape, sizeof(escape));
if (status != ARES_SUCCESS) {
goto fail;
}
continue;
}
/* Reserved characters need to be escaped, otherwise normal */
if (is_reservedch(c)) {
status = ares__buf_append_byte(dest, '\\');
if (status != ARES_SUCCESS) {
goto fail;
}
}
status = ares__buf_append_byte(dest, c);
if (status != ARES_SUCCESS) {
return status;
}
}
return ares__buf_consume(buf, len);
fail:
return status;
}
size_t ares__buf_consume_whitespace(ares__buf_t *buf,
ares_bool_t include_linefeed)
{
@ -793,161 +764,6 @@ ares_status_t ares__buf_set_position(ares__buf_t *buf, size_t idx)
return ARES_SUCCESS;
}
#define ARES_DNS_HEADER_SIZE 12
ares_status_t ares__buf_parse_dns_name(ares__buf_t *buf, char **name,
ares_bool_t is_hostname)
{
size_t save_offset = 0;
unsigned char c;
ares_status_t status;
ares__buf_t *namebuf = NULL;
size_t label_start = ares__buf_get_position(buf);
if (buf == NULL) {
return ARES_EFORMERR;
}
if (name != NULL) {
namebuf = ares__buf_create();
if (namebuf == NULL) {
status = ARES_ENOMEM;
goto fail;
}
}
/* XXX: LibraryTest.ExpandName and LibraryTest.ExpandNameFailure are not
* complete DNS messages but rather non-valid minimized snippets to try
* to test the old parser, so we have to turn off this sanity check
* it appears until these test cases are rewritten
*/
#if 0
if (ares__buf_get_position(buf) < ARES_DNS_HEADER_SIZE) {
status = ARES_EFORMERR;
goto fail;
}
#endif
/* The compression scheme allows a domain name in a message to be
* represented as either:
*
* - a sequence of labels ending in a zero octet
* - a pointer
* - a sequence of labels ending with a pointer
*/
while (1) {
/* Keep track of the minimum label starting position to prevent forward
* jumping */
if (label_start > ares__buf_get_position(buf)) {
label_start = ares__buf_get_position(buf);
}
status = ares__buf_fetch_bytes(buf, &c, 1);
if (status != ARES_SUCCESS) {
goto fail;
}
/* Pointer/Redirect */
if ((c & 0xc0) == 0xc0) {
/* The pointer takes the form of a two octet sequence:
*
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
* | 1 1| OFFSET |
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
*
* The first two bits are ones. This allows a pointer to be distinguished
* from a label, since the label must begin with two zero bits because
* labels are restricted to 63 octets or less. (The 10 and 01
* combinations are reserved for future use.) The OFFSET field specifies
* an offset from the start of the message (i.e., the first octet of the
* ID field in the domain header). A zero offset specifies the first byte
* of the ID field, etc.
*/
size_t offset = (size_t)((c & 0x3F) << 8);
/* Fetch second byte of the redirect length */
status = ares__buf_fetch_bytes(buf, &c, 1);
if (status != ARES_SUCCESS) {
goto fail;
}
offset |= ((size_t)c);
/* According to RFC 1035 4.1.4:
* In this scheme, an entire domain name or a list of labels at
* the end of a domain name is replaced with a pointer to a prior
* occurance of the same name.
* Note the word "prior", meaning it must go backwards. This was
* confirmed via the ISC BIND code that it also prevents forward
* pointers.
*/
if (offset >= label_start) {
status = ARES_EBADNAME;
goto fail;
}
/* First time we make a jump, save the current position */
if (save_offset == 0) {
save_offset = ares__buf_get_position(buf);
}
status = ares__buf_set_position(buf, offset);
if (status != ARES_SUCCESS) {
status = ARES_EBADNAME;
goto fail;
}
continue;
} else if ((c & 0xc0) != 0) {
/* 10 and 01 are reserved */
status = ARES_EBADNAME;
goto fail;
} else if (c == 0) {
/* termination via zero octet*/
break;
}
/* New label */
/* Labels are separated by periods */
if (ares__buf_len(namebuf) != 0 && name != NULL) {
status = ares__buf_append_byte(namebuf, '.');
if (status != ARES_SUCCESS) {
goto fail;
}
}
status = ares__buf_fetch_dnsname_into_buf(buf, namebuf, c, is_hostname);
if (status != ARES_SUCCESS) {
goto fail;
}
}
/* Restore offset read after first redirect/pointer as this is where the DNS
* message continues */
if (save_offset) {
ares__buf_set_position(buf, save_offset);
}
if (name != NULL) {
*name = ares__buf_finish_str(namebuf, NULL);
if (*name == NULL) {
status = ARES_ENOMEM;
goto fail;
}
}
return ARES_SUCCESS;
fail:
/* We want badname response if we couldn't parse */
if (status == ARES_EBADRESP) {
status = ARES_EBADNAME;
}
ares__buf_destroy(namebuf);
return status;
}
ares_status_t ares__buf_parse_dns_binstr(ares__buf_t *buf, size_t remaining_len,
unsigned char **bin, size_t *bin_len,
ares_bool_t allow_multiple)

@ -90,6 +90,37 @@ ares_status_t ares__buf_append(ares__buf_t *buf, const unsigned char *data,
*/
ares_status_t ares__buf_append_byte(ares__buf_t *buf, unsigned char byte);
/*! Append a 16bit Big Endian number to the buffer.
*
* \param[in] buf Initialized buffer object
* \param[out] u16 16bit integer
* \return ARES_SUCCESS or one of the c-ares error codes
*/
ares_status_t ares__buf_append_be16(ares__buf_t *buf, unsigned short u16);
/*! Append a 32bit Big Endian number to the buffer.
*
* \param[in] buf Initialized buffer object
* \param[out] u32 32bit integer
* \return ARES_SUCCESS or one of the c-ares error codes
*/
ares_status_t ares__buf_append_be32(ares__buf_t *buf, unsigned int u32);
/*! Sets the current buffer length. This *may* be used if there is a need to
* override a prior position in the buffer, such as if there is a length
* prefix that isn't easily predictable, and you must go back and overwrite
* that position.
*
* Only valid on non-const buffers. Length provided must not exceed current
* allocated buffer size, but otherwise there are very few protections on
* this function. Use cautiously.
*
* \param[in] buf Initialized buffer object
* \param[in] len Length to set
* \return ARES_SUCCESS or one of the c-ares error codes
*/
ares_status_t ares__buf_set_length(ares__buf_t *buf, size_t len);
/*! Start a dynamic append operation that returns a buffer suitable for
* writing. A desired minimum length is passed in, and the actual allocated
@ -392,24 +423,6 @@ ares_status_t ares__buf_set_position(ares__buf_t *buf, size_t idx);
*/
size_t ares__buf_get_position(const ares__buf_t *buf);
/*! Parse a compressed DNS name as defined in RFC1035 starting at the current
* offset within the buffer.
*
* It is assumed that either a const buffer is being used, or before
* the message processing was started that ares__buf_reclaim() was called.
*
* \param[in] buf Initialized buffer object
* \param[out] name Pointer passed by reference to be filled in with
* allocated string of the parsed name that must be
* ares_free()'d by the caller.
* \param[in] is_hostname if ARES_TRUE, will validate the character set for
* a valid hostname or will return error.
* \return ARES_SUCCESS on success
*/
ares_status_t ares__buf_parse_dns_name(ares__buf_t *buf, char **name,
ares_bool_t is_hostname);
/*! Parse a character-string as defined in RFC1035, as a null-terminated
* string.
*

@ -1,7 +1,6 @@
/* MIT License
*
* Copyright (c) Massachusetts Institute of Technology
* Copyright (c) The c-ares project and its contributors
* Copyright (c) 2023 Brad House
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@ -26,183 +25,88 @@
*/
#include "ares_setup.h"
#ifdef HAVE_NETINET_IN_H
# include <netinet/in.h>
#endif
#include "ares_nameser.h"
#include "ares.h"
#include "ares_dns.h"
#include "ares_private.h"
/* Header format, from RFC 1035:
* 1 1 1 1 1 1
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
* | ID |
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
* |QR| Opcode |AA|TC|RD|RA| Z | RCODE |
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
* | QDCOUNT |
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
* | ANCOUNT |
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
* | NSCOUNT |
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
* | ARCOUNT |
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
*
* AA, TC, RA, and RCODE are only set in responses. Brief description
* of the remaining fields:
* ID Identifier to match responses with queries
* QR Query (0) or response (1)
* Opcode For our purposes, always O_QUERY
* RD Recursion desired
* Z Reserved (zero)
* QDCOUNT Number of queries
* ANCOUNT Number of answers
* NSCOUNT Number of name server records
* ARCOUNT Number of additional records
*
* Question format, from RFC 1035:
* 1 1 1 1 1 1
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
* | |
* / QNAME /
* / /
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
* | QTYPE |
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
* | QCLASS |
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
*
* The query name is encoded as a series of labels, each represented
* as a one-byte length (maximum 63) followed by the text of the
* label. The list is terminated by a label of length zero (which can
* be thought of as the root domain).
*/
int ares_create_query(const char *name, int dnsclass, int type,
unsigned short id, int rd, unsigned char **bufp,
int *buflenp, int max_udp_size)
{
size_t len;
unsigned char *q;
const char *p;
size_t buflen;
unsigned char *buf;
ares_status_t status;
ares_dns_record_t *dnsrec = NULL;
size_t len;
if (name == NULL || bufp == NULL || buflenp == NULL) {
status = ARES_EFORMERR;
goto done;
}
/* Set our results early, in case we bail out early with an error. */
*buflenp = 0;
*bufp = NULL;
*buflenp = 0;
/* Per RFC 7686, reject queries for ".onion" domain names with NXDOMAIN. */
if (ares__is_onion_domain(name)) {
return ARES_ENOTFOUND;
status = ARES_ENOTFOUND;
goto done;
}
/* Allocate a memory area for the maximum size this packet might need. +2
* is for the length byte and zero termination if no dots or ecscaping is
* used.
*/
len = ares_strlen(name) + 2 + HFIXEDSZ + QFIXEDSZ +
(max_udp_size ? EDNSFIXEDSZ : 0);
buf = ares_malloc(len);
if (!buf) {
return ARES_ENOMEM;
status = ares_dns_record_create(&dnsrec, id, rd ? ARES_FLAG_RD : 0,
ARES_OPCODE_QUERY, ARES_RCODE_NOERROR);
if (status != ARES_SUCCESS) {
goto done;
}
/* Set up the header. */
q = buf;
memset(q, 0, HFIXEDSZ);
DNS_HEADER_SET_QID(q, id);
DNS_HEADER_SET_OPCODE(q, O_QUERY);
if (rd) {
DNS_HEADER_SET_RD(q, 1);
} else {
DNS_HEADER_SET_RD(q, 0);
status = ares_dns_record_query_add(dnsrec, name, (ares_dns_rec_type_t)type,
(ares_dns_class_t)dnsclass);
if (status != ARES_SUCCESS) {
goto done;
}
DNS_HEADER_SET_QDCOUNT(q, 1);
if (max_udp_size) {
DNS_HEADER_SET_ARCOUNT(q, 1);
}
/* max_udp_size > 0 indicates EDNS, so send OPT RR as an additional record */
if (max_udp_size > 0) {
ares_dns_rr_t *rr = NULL;
/* A name of "." is a screw case for the loop below, so adjust it. */
if (strcmp(name, ".") == 0) {
name++;
}
status = ares_dns_record_rr_add(&rr, dnsrec, ARES_SECTION_ADDITIONAL, "",
ARES_REC_TYPE_OPT, ARES_CLASS_IN, 0);
if (status != ARES_SUCCESS) {
goto done;
}
/* Start writing out the name after the header. */
q += HFIXEDSZ;
while (*name) {
if (*name == '.') {
ares_free(buf);
return ARES_EBADNAME;
if (max_udp_size > 65535) {
status = ARES_EFORMERR;
goto done;
}
/* Count the number of bytes in this label. */
len = 0;
for (p = name; *p && *p != '.'; p++) {
if (*p == '\\' && *(p + 1) != 0) {
p++;
}
len++;
status = ares_dns_rr_set_u16(rr, ARES_RR_OPT_UDP_SIZE,
(unsigned short)max_udp_size);
if (status != ARES_SUCCESS) {
goto done;
}
if (len > MAXLABEL) {
ares_free(buf);
return ARES_EBADNAME;
status = ares_dns_rr_set_u8(rr, ARES_RR_OPT_EXT_RCODE, 0);
if (status != ARES_SUCCESS) {
goto done;
}
/* Encode the length and copy the data. */
*q++ = (unsigned char)len;
for (p = name; *p && *p != '.'; p++) {
if (*p == '\\' && *(p + 1) != 0) {
p++;
}
*q++ = (unsigned char)*p;
status = ares_dns_rr_set_u8(rr, ARES_RR_OPT_VERSION, 0);
if (status != ARES_SUCCESS) {
goto done;
}
/* Go to the next label and repeat, unless we hit the end. */
if (!*p) {
break;
status = ares_dns_rr_set_u16(rr, ARES_RR_OPT_FLAGS, 0);
if (status != ARES_SUCCESS) {
goto done;
}
name = p + 1;
}
/* Add the zero-length label at the end. */
*q++ = 0;
/* Finish off the question with the type and class. */
DNS_QUESTION_SET_TYPE(q, type);
DNS_QUESTION_SET_CLASS(q, dnsclass);
q += QFIXEDSZ;
if (max_udp_size) {
memset(q, 0, EDNSFIXEDSZ);
q++;
DNS_RR_SET_TYPE(q, T_OPT);
DNS_RR_SET_CLASS(q, max_udp_size);
q += (EDNSFIXEDSZ - 1);
}
buflen = (size_t)(q - buf);
/* Reject names that are longer than the maximum of 255 bytes that's
* specified in RFC 1035 ("To simplify implementations, the total length of
* a domain name (i.e., label octets and label length octets) is restricted
* to 255 octets or less."). */
if (buflen > (size_t)(MAXCDNAME + HFIXEDSZ + QFIXEDSZ +
(max_udp_size ? EDNSFIXEDSZ : 0))) {
ares_free(buf);
return ARES_EBADNAME;
status = ares_dns_write(dnsrec, bufp, &len);
if (status != ARES_SUCCESS) {
goto done;
}
/* we know this fits in an int at this point */
*buflenp = (int)buflen;
*bufp = buf;
*buflenp = (int)len;
return ARES_SUCCESS;
done:
ares_dns_record_destroy(dnsrec);
return (int)status;
}

@ -106,6 +106,27 @@ ares_bool_t ares_dns_rec_type_isvalid(ares_dns_rec_type_t type,
return is_query ? ARES_TRUE : ARES_FALSE;
}
ares_bool_t ares_dns_rec_type_allow_name_compression(ares_dns_rec_type_t type)
{
/* Only record types defined in RFC1035 allow name compression within the
* RDATA. Otherwise nameservers that don't understand an RR may not be
* able to pass along the RR in a proper manner */
switch (type) {
case ARES_REC_TYPE_A:
case ARES_REC_TYPE_NS:
case ARES_REC_TYPE_CNAME:
case ARES_REC_TYPE_SOA:
case ARES_REC_TYPE_PTR:
case ARES_REC_TYPE_HINFO:
case ARES_REC_TYPE_MX:
case ARES_REC_TYPE_TXT:
return ARES_TRUE;
default:
break;
}
return ARES_FALSE;
}
ares_bool_t ares_dns_class_isvalid(ares_dns_class_t qclass,
ares_bool_t is_query)
{

@ -0,0 +1,675 @@
/* MIT License
*
* Copyright (c) 2023 Brad House
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* SPDX-License-Identifier: MIT
*/
#include "ares_setup.h"
#include "ares.h"
#include "ares_private.h"
typedef struct {
char *name;
size_t name_len;
size_t idx;
} ares_nameoffset_t;
static void ares__nameoffset_free(void *arg)
{
ares_nameoffset_t *off = arg;
if (off == NULL) {
return;
}
ares_free(off->name);
ares_free(off);
}
static ares_status_t ares__nameoffset_create(ares__llist_t **list,
const char *name, size_t idx)
{
ares_status_t status;
ares_nameoffset_t *off = NULL;
if (list == NULL || name == NULL || ares_strlen(name) == 0 ||
ares_strlen(name) > 255) {
return ARES_EFORMERR;
}
if (*list == NULL) {
*list = ares__llist_create(ares__nameoffset_free);
}
if (*list == NULL) {
status = ARES_ENOMEM;
goto fail;
}
off = ares_malloc_zero(sizeof(*off));
if (off == NULL) {
return ARES_ENOMEM;
}
off->name = ares_strdup(name);
off->name_len = ares_strlen(off->name);
off->idx = idx;
if (ares__llist_insert_last(*list, off) == NULL) {
status = ARES_ENOMEM;
goto fail;
}
status = ARES_SUCCESS;
fail:
ares__nameoffset_free(off);
return status;
}
static const ares_nameoffset_t *ares__nameoffset_find(ares__llist_t *list,
const char *name)
{
size_t name_len = ares_strlen(name);
ares__llist_node_t *node;
const ares_nameoffset_t *longest_match = NULL;
if (list == NULL || name == NULL || name_len == 0) {
return NULL;
}
for (node = ares__llist_node_first(list); node != NULL;
node = ares__llist_node_next(node)) {
const ares_nameoffset_t *val = ares__llist_node_val(node);
size_t prefix_len;
/* Can't be a match if the stored name is longer */
if (val->name_len > name_len) {
continue;
}
/* Can't be the longest match if our existing longest match is longer */
if (longest_match != NULL && longest_match->name_len > val->name_len) {
continue;
}
prefix_len = name_len - val->name_len;
if (strcasecmp(val->name, name + prefix_len) != 0) {
continue;
}
/* We need to make sure if `val->name` is "example.com" that name is
* is separated by a label, e.g. "myexample.com" is not ok, however
* "my.example.com" is, so we look for the preceding "." */
if (prefix_len != 0 && name[prefix_len - 1] != '.') {
continue;
}
longest_match = val;
}
return longest_match;
}
typedef struct {
ares__buf_t **label;
size_t num;
} ares_dns_labels_t;
static void ares_dns_labels_free(ares_dns_labels_t *labels)
{
size_t i;
if (labels == NULL) {
return;
}
for (i = 0; i < labels->num; i++) {
ares__buf_destroy(labels->label[i]);
labels->label[i] = NULL;
}
ares_free(labels->label);
labels->label = NULL;
labels->num = 0;
}
static ares__buf_t *ares_dns_labels_add(ares_dns_labels_t *labels)
{
void *temp;
if (labels == NULL) {
return NULL;
}
temp = ares_realloc_zero(labels->label, sizeof(*labels->label) * labels->num,
sizeof(*labels->label) * (labels->num + 1));
if (temp == NULL) {
return NULL;
}
labels->label = temp;
labels->label[labels->num] = ares__buf_create();
if (labels->label[labels->num] == NULL) {
return NULL;
}
labels->num++;
return labels->label[labels->num - 1];
}
static ares__buf_t *ares_dns_labels_get_last(ares_dns_labels_t *labels)
{
if (labels == NULL || labels->num == 0) {
return NULL;
}
return labels->label[labels->num - 1];
}
static void ares_dns_name_labels_del_last(ares_dns_labels_t *labels)
{
if (labels == NULL || labels->num == 0) {
return;
}
ares__buf_destroy(labels->label[labels->num - 1]);
labels->label[labels->num - 1] = NULL;
labels->num--;
}
static ares_status_t ares_parse_dns_name_escape(ares__buf_t *namebuf,
ares__buf_t *label,
ares_bool_t validate_hostname)
{
ares_status_t status;
unsigned char c;
status = ares__buf_fetch_bytes(namebuf, &c, 1);
if (status != ARES_SUCCESS) {
return ARES_EBADNAME;
}
/* If next character is a digit, read 2 more digits */
if (isdigit(c)) {
size_t i;
unsigned int val = 0;
val = c - '0';
for (i = 0; i < 2; i++) {
status = ares__buf_fetch_bytes(namebuf, &c, 1);
if (status != ARES_SUCCESS) {
return ARES_EBADNAME;
}
if (!isdigit(c)) {
return ARES_EBADNAME;
}
val *= 10;
val += c - '0';
}
/* Out of range */
if (val > 255) {
return ARES_EBADNAME;
}
if (validate_hostname && !ares__is_hostnamech((unsigned char)val)) {
return ARES_EBADNAME;
}
return ares__buf_append_byte(label, (unsigned char)val);
}
/* We can just output the character */
if (validate_hostname && !ares__is_hostnamech(c)) {
return ARES_EBADNAME;
}
return ares__buf_append_byte(label, c);
}
static ares_status_t ares_split_dns_name(ares_dns_labels_t *labels,
ares_bool_t validate_hostname,
const char *name)
{
ares_status_t status;
ares__buf_t *label = NULL;
ares__buf_t *namebuf = NULL;
size_t i;
size_t total_len = 0;
unsigned char c;
if (name == NULL || labels == NULL) {
return ARES_EFORMERR;
}
/* Put name into a buffer for parsing */
namebuf = ares__buf_create();
if (namebuf == NULL) {
status = ARES_ENOMEM;
goto done;
}
if (*name != '\0') {
status =
ares__buf_append(namebuf, (unsigned char *)name, ares_strlen(name));
if (status != ARES_SUCCESS) {
goto done;
}
}
/* Start with 1 label */
label = ares_dns_labels_add(labels);
if (labels == NULL) {
status = ARES_ENOMEM;
goto done;
}
while (ares__buf_fetch_bytes(namebuf, &c, 1) == ARES_SUCCESS) {
/* New label */
if (c == '.') {
label = ares_dns_labels_add(labels);
if (labels == NULL) {
status = ARES_ENOMEM;
goto done;
}
continue;
}
/* Escape */
if (c == '\\') {
status = ares_parse_dns_name_escape(namebuf, label, validate_hostname);
if (status != ARES_SUCCESS) {
goto done;
}
continue;
}
/* Output direct character */
if (validate_hostname && !ares__is_hostnamech(c)) {
status = ARES_EBADNAME;
goto done;
}
status = ares__buf_append_byte(label, c);
if (status != ARES_SUCCESS) {
goto done;
}
}
/* Remove trailing blank label */
if (ares__buf_len(ares_dns_labels_get_last(labels)) == 0) {
ares_dns_name_labels_del_last(labels);
}
/* If someone passed in "." there could have been 2 blank labels, check for
* that */
if (labels->num == 1 &&
ares__buf_len(ares_dns_labels_get_last(labels)) == 0) {
ares_dns_name_labels_del_last(labels);
}
/* Scan to make sure label lengths are valid */
for (i = 0; i < labels->num; i++) {
size_t len = ares__buf_len(labels->label[i]);
/* No 0-length labels, and no labels over 63 bytes */
if (len == 0 || len > 63) {
status = ARES_EBADNAME;
goto done;
}
total_len += len;
}
/* Can't exceed maximum (unescaped) length */
if (labels->num && total_len + labels->num - 1 > 255) {
status = ARES_EBADNAME;
goto done;
}
status = ARES_SUCCESS;
done:
ares__buf_destroy(namebuf);
if (status != ARES_SUCCESS) {
ares_dns_labels_free(labels);
}
return status;
}
ares_status_t ares__dns_name_write(ares__buf_t *buf, ares__llist_t **list,
ares_bool_t validate_hostname,
const char *name)
{
const ares_nameoffset_t *off = NULL;
size_t name_len;
size_t pos = ares__buf_get_position(buf);
ares_dns_labels_t labels;
char name_copy[512];
ares_status_t status;
if (buf == NULL || name == NULL) {
return ARES_EFORMERR;
}
memset(&labels, 0, sizeof(labels));
/* NOTE: due to possible escaping, name_copy buffer is > 256 to allow for
* this */
name_len = ares_strcpy(name_copy, name, sizeof(name_copy));
/* Find longest match */
if (list != NULL) {
off = ares__nameoffset_find(*list, name_copy);
if (off != NULL && off->name_len != name_len) {
/* truncate */
name_len -= (off->name_len + 1);
name_copy[name_len - 1] = 0;
}
}
/* Output labels */
if (off == NULL || off->name_len != name_len) {
size_t i;
status = ares_split_dns_name(&labels, validate_hostname, name_copy);
if (status != ARES_SUCCESS) {
goto done;
}
for (i = 0; i < labels.num; i++) {
size_t len = 0;
const unsigned char *ptr = ares__buf_peek(labels.label[i], &len);
status = ares__buf_append_byte(buf, (unsigned char)(len & 0xFF));
if (status != ARES_SUCCESS) {
goto done;
}
status = ares__buf_append(buf, ptr, len);
if (status != ARES_SUCCESS) {
goto done;
}
}
/* If we are NOT jumping to another label, output terminator */
if (off == NULL) {
status = ares__buf_append_byte(buf, 0);
if (status != ARES_SUCCESS) {
goto done;
}
}
}
/* Output name compression offset jump */
if (off != NULL) {
unsigned short u16 =
(unsigned short)0xC000 | (unsigned short)(off->idx & 0x3FFF);
status = ares__buf_append_be16(buf, u16);
if (status != ARES_SUCCESS) {
goto done;
}
}
/* Store pointer for future jumps as long as its not an exact match for
* a prior entry */
if (list != NULL && off != NULL && off->name_len != name_len &&
name_len > 0) {
status = ares__nameoffset_create(list, name /* not truncated copy! */, pos);
if (status != ARES_SUCCESS) {
goto done;
}
}
status = ARES_SUCCESS;
done:
ares_dns_labels_free(&labels);
return status;
}
/* Reserved characters for names that need to be escaped */
static ares_bool_t is_reservedch(int ch)
{
switch (ch) {
case '"':
case '.':
case ';':
case '\\':
case '(':
case ')':
case '@':
case '$':
return ARES_TRUE;
default:
break;
}
return ARES_FALSE;
}
static ares_status_t ares__fetch_dnsname_into_buf(ares__buf_t *buf,
ares__buf_t *dest, size_t len,
ares_bool_t is_hostname)
{
size_t remaining_len;
const unsigned char *ptr = ares__buf_peek(buf, &remaining_len);
ares_status_t status;
size_t i;
if (buf == NULL || len == 0 || remaining_len < len) {
return ARES_EBADRESP;
}
for (i = 0; i < len; i++) {
unsigned char c = ptr[i];
/* Hostnames have a very specific allowed character set. Anything outside
* of that (non-printable and reserved included) are disallowed */
if (is_hostname && !ares__is_hostnamech(c)) {
status = ARES_EBADRESP;
goto fail;
}
/* NOTE: dest may be NULL if the user is trying to skip the name. validation
* still occurs above. */
if (dest == NULL) {
continue;
}
/* Non-printable characters need to be output as \DDD */
if (!ares__isprint(c)) {
unsigned char escape[4];
escape[0] = '\\';
escape[1] = '0' + (c / 100);
escape[2] = '0' + ((c % 100) / 10);
escape[3] = '0' + (c % 10);
status = ares__buf_append(dest, escape, sizeof(escape));
if (status != ARES_SUCCESS) {
goto fail;
}
continue;
}
/* Reserved characters need to be escaped, otherwise normal */
if (is_reservedch(c)) {
status = ares__buf_append_byte(dest, '\\');
if (status != ARES_SUCCESS) {
goto fail;
}
}
status = ares__buf_append_byte(dest, c);
if (status != ARES_SUCCESS) {
return status;
}
}
return ares__buf_consume(buf, len);
fail:
return status;
}
ares_status_t ares__dns_name_parse(ares__buf_t *buf, char **name,
ares_bool_t is_hostname)
{
size_t save_offset = 0;
unsigned char c;
ares_status_t status;
ares__buf_t *namebuf = NULL;
size_t label_start = ares__buf_get_position(buf);
if (buf == NULL) {
return ARES_EFORMERR;
}
if (name != NULL) {
namebuf = ares__buf_create();
if (namebuf == NULL) {
status = ARES_ENOMEM;
goto fail;
}
}
/* The compression scheme allows a domain name in a message to be
* represented as either:
*
* - a sequence of labels ending in a zero octet
* - a pointer
* - a sequence of labels ending with a pointer
*/
while (1) {
/* Keep track of the minimum label starting position to prevent forward
* jumping */
if (label_start > ares__buf_get_position(buf)) {
label_start = ares__buf_get_position(buf);
}
status = ares__buf_fetch_bytes(buf, &c, 1);
if (status != ARES_SUCCESS) {
goto fail;
}
/* Pointer/Redirect */
if ((c & 0xc0) == 0xc0) {
/* The pointer takes the form of a two octet sequence:
*
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
* | 1 1| OFFSET |
* +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
*
* The first two bits are ones. This allows a pointer to be distinguished
* from a label, since the label must begin with two zero bits because
* labels are restricted to 63 octets or less. (The 10 and 01
* combinations are reserved for future use.) The OFFSET field specifies
* an offset from the start of the message (i.e., the first octet of the
* ID field in the domain header). A zero offset specifies the first byte
* of the ID field, etc.
*/
size_t offset = (size_t)((c & 0x3F) << 8);
/* Fetch second byte of the redirect length */
status = ares__buf_fetch_bytes(buf, &c, 1);
if (status != ARES_SUCCESS) {
goto fail;
}
offset |= ((size_t)c);
/* According to RFC 1035 4.1.4:
* In this scheme, an entire domain name or a list of labels at
* the end of a domain name is replaced with a pointer to a prior
* occurance of the same name.
* Note the word "prior", meaning it must go backwards. This was
* confirmed via the ISC BIND code that it also prevents forward
* pointers.
*/
if (offset >= label_start) {
status = ARES_EBADNAME;
goto fail;
}
/* First time we make a jump, save the current position */
if (save_offset == 0) {
save_offset = ares__buf_get_position(buf);
}
status = ares__buf_set_position(buf, offset);
if (status != ARES_SUCCESS) {
status = ARES_EBADNAME;
goto fail;
}
continue;
} else if ((c & 0xc0) != 0) {
/* 10 and 01 are reserved */
status = ARES_EBADNAME;
goto fail;
} else if (c == 0) {
/* termination via zero octet*/
break;
}
/* New label */
/* Labels are separated by periods */
if (ares__buf_len(namebuf) != 0 && name != NULL) {
status = ares__buf_append_byte(namebuf, '.');
if (status != ARES_SUCCESS) {
goto fail;
}
}
status = ares__fetch_dnsname_into_buf(buf, namebuf, c, is_hostname);
if (status != ARES_SUCCESS) {
goto fail;
}
}
/* Restore offset read after first redirect/pointer as this is where the DNS
* message continues */
if (save_offset) {
ares__buf_set_position(buf, save_offset);
}
if (name != NULL) {
*name = ares__buf_finish_str(namebuf, NULL);
if (*name == NULL) {
status = ARES_ENOMEM;
goto fail;
}
}
return ARES_SUCCESS;
fail:
/* We want badname response if we couldn't parse */
if (status == ARES_EBADRESP) {
status = ARES_EBADNAME;
}
ares__buf_destroy(namebuf);
return status;
}

@ -50,7 +50,7 @@ static ares_status_t ares_dns_parse_and_set_dns_name(ares__buf_t *buf,
ares_status_t status;
char *name = NULL;
status = ares__buf_parse_dns_name(buf, &name, is_hostname);
status = ares__dns_name_parse(buf, &name, is_hostname);
if (status != ARES_SUCCESS) {
return status;
}
@ -773,7 +773,7 @@ static ares_status_t ares_dns_parse_qd(ares__buf_t *buf,
*/
/* Name */
status = ares__buf_parse_dns_name(buf, &name, ARES_FALSE);
status = ares__dns_name_parse(buf, &name, ARES_FALSE);
if (status != ARES_SUCCESS) {
goto done;
}
@ -845,7 +845,7 @@ static ares_status_t ares_dns_parse_rr(ares__buf_t *buf, unsigned int flags,
*/
/* Name */
status = ares__buf_parse_dns_name(buf, &name, ARES_FALSE);
status = ares__dns_name_parse(buf, &name, ARES_FALSE);
if (status != ARES_SUCCESS) {
goto done;
}

@ -414,6 +414,48 @@ ares_status_t ares_dns_record_rr_add(ares_dns_rr_t **rr_out,
return ARES_SUCCESS;
}
ares_status_t ares_dns_record_rr_del(ares_dns_record_t *dnsrec,
ares_dns_section_t sect, size_t idx)
{
ares_dns_rr_t *rr_ptr = NULL;
size_t *rr_len = NULL;
size_t cnt_after;
if (dnsrec == NULL || !ares_dns_section_isvalid(sect)) {
return ARES_EFORMERR;
}
switch (sect) {
case ARES_SECTION_ANSWER:
rr_ptr = dnsrec->an;
rr_len = &dnsrec->ancount;
break;
case ARES_SECTION_AUTHORITY:
rr_ptr = dnsrec->ns;
rr_len = &dnsrec->nscount;
break;
case ARES_SECTION_ADDITIONAL:
rr_ptr = dnsrec->ar;
rr_len = &dnsrec->arcount;
break;
}
if (idx >= *rr_len) {
return ARES_EFORMERR;
}
ares__dns_rr_free(&rr_ptr[idx]);
cnt_after = *rr_len - idx - 1;
if (cnt_after) {
memmove(&rr_ptr[idx], &rr_ptr[idx + 1], sizeof(*rr_ptr) * cnt_after);
}
(*rr_len)--;
return ARES_SUCCESS;
}
ares_dns_rr_t *ares_dns_record_rr_get(ares_dns_record_t *dnsrec,
ares_dns_section_t sect, size_t idx)
{

@ -403,12 +403,22 @@ ares_status_t ares_dns_record_rr_add(ares_dns_rr_t **rr_out,
* \param[in] dnsrec Initialized record object
* \param[in] sect Section for resource record
* \param[in] idx Index of resource record in section
* \param NULL on misuse, otherwise a pointer to the resource record
* \return NULL on misuse, otherwise a pointer to the resource record
*/
ares_dns_rr_t *ares_dns_record_rr_get(ares_dns_record_t *dnsrec,
ares_dns_section_t sect, size_t idx);
/*! Remove the resource record based on the section and index
*
* \param[in] dnsrec Initialized record object
* \param[in] sect Section for resource record
* \param[in] idx Index of resource record in section
* \return ARES_SUCCESS on success, otherwise an error code.
*/
ares_status_t ares_dns_record_rr_del(ares_dns_record_t *dnsrec,
ares_dns_section_t sect, size_t idx);
/*! Retrieve a list of Resource Record keys that can be set or retrieved for
* the Resource record type.
*
@ -625,18 +635,28 @@ const unsigned char *ares_dns_rr_get_bin(const ares_dns_rr_t *dns_rr,
ares_status_t ares_dns_parse(const unsigned char *buf, size_t buf_len,
unsigned int flags, ares_dns_record_t **dnsrec);
/*! Write a complete DNS message
*
* \param[in] dnsrec Pointer to initialized and filled DNS record object.
* \param[out] buf Pointer passed by reference to be filled in with with
* DNS message. Must be ares_free()'d by caller.
* \param[out] buf_len Length of returned buffer containing DNS message.
* \return ARES_SUCCESS on success
*/
ares_status_t ares_dns_write(ares_dns_record_t *dnsrec, unsigned char **buf,
size_t *buf_len);
/*! @} */
/* ---- PRIVATE BELOW ----- */
ares_bool_t ares_dns_opcode_isvalid(ares_dns_opcode_t opcode);
ares_bool_t ares_dns_rcode_isvalid(ares_dns_rcode_t rcode);
ares_bool_t ares_dns_flags_arevalid(unsigned short flags);
ares_bool_t ares_dns_rec_type_isvalid(ares_dns_rec_type_t type,
ares_bool_t is_query);
ares_bool_t ares_dns_class_isvalid(ares_dns_class_t qclass,
ares_bool_t is_query);
ares_bool_t ares_dns_section_isvalid(ares_dns_section_t sect);
ares_bool_t ares_dns_rec_type_allow_name_compression(ares_dns_rec_type_t type);
ares_bool_t ares_dns_opcode_isvalid(ares_dns_opcode_t opcode);
ares_bool_t ares_dns_rcode_isvalid(ares_dns_rcode_t rcode);
ares_bool_t ares_dns_flags_arevalid(unsigned short flags);
ares_bool_t ares_dns_rec_type_isvalid(ares_dns_rec_type_t type,
ares_bool_t is_query);
ares_bool_t ares_dns_class_isvalid(ares_dns_class_t qclass,
ares_bool_t is_query);
ares_bool_t ares_dns_section_isvalid(ares_dns_section_t sect);
ares_status_t ares_dns_rr_set_str_own(ares_dns_rr_t *dns_rr,
ares_dns_rr_key_t key, char *val);
ares_status_t ares_dns_rr_set_bin_own(ares_dns_rr_t *dns_rr,

@ -0,0 +1,847 @@
/* MIT License
*
* Copyright (c) 2023 Brad House
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* SPDX-License-Identifier: MIT
*/
#include "ares_setup.h"
#include "ares.h"
#include "ares_private.h"
#include "ares_dns_record.h"
#include <limits.h>
#ifdef HAVE_STDINT_H
# include <stdint.h>
#endif
static ares_status_t ares_dns_write_header(const ares_dns_record_t *dnsrec,
ares__buf_t *buf)
{
unsigned short u16;
unsigned short opcode;
unsigned short rcode;
ares_status_t status;
/* ID */
status = ares__buf_append_be16(buf, dnsrec->id);
if (status != ARES_SUCCESS) {
return status;
}
/* Flags */
u16 = 0;
/* QR */
if (dnsrec->flags & ARES_FLAG_QR) {
u16 |= 0x8000;
}
/* OPCODE */
opcode = (unsigned short)(dnsrec->opcode & 0xF);
opcode <<= 11;
u16 |= opcode;
/* AA */
if (dnsrec->flags & ARES_FLAG_AA) {
u16 |= 0x400;
}
/* TC */
if (dnsrec->flags & ARES_FLAG_TC) {
u16 |= 0x200;
}
/* RD */
if (dnsrec->flags & ARES_FLAG_RD) {
u16 |= 0x100;
}
/* RA */
if (dnsrec->flags & ARES_FLAG_RA) {
u16 |= 0x80;
}
/* Z -- unused */
/* AD */
if (dnsrec->flags & ARES_FLAG_AD) {
u16 |= 0x20;
}
/* CD */
if (dnsrec->flags & ARES_FLAG_CD) {
u16 |= 0x10;
}
/* RCODE */
rcode = (unsigned short)(dnsrec->rcode & 0xF);
u16 |= rcode;
status = ares__buf_append_be16(buf, u16);
if (status != ARES_SUCCESS) {
return status;
}
/* QDCOUNT */
status = ares__buf_append_be16(buf, (unsigned short)dnsrec->qdcount);
if (status != ARES_SUCCESS) {
return status;
}
/* ANCOUNT */
status = ares__buf_append_be16(buf, (unsigned short)dnsrec->ancount);
if (status != ARES_SUCCESS) {
return status;
}
/* NSCOUNT */
status = ares__buf_append_be16(buf, (unsigned short)dnsrec->nscount);
if (status != ARES_SUCCESS) {
return status;
}
/* ARCOUNT */
status = ares__buf_append_be16(buf, (unsigned short)dnsrec->arcount);
if (status != ARES_SUCCESS) {
return status;
}
return ARES_SUCCESS;
}
static ares_status_t ares_dns_write_questions(const ares_dns_record_t *dnsrec,
ares__llist_t **namelist,
ares__buf_t *buf)
{
size_t i;
for (i = 0; i < ares_dns_record_query_cnt(dnsrec); i++) {
ares_status_t status;
const char *name = NULL;
ares_dns_rec_type_t qtype;
ares_dns_class_t qclass;
status = ares_dns_record_query_get(dnsrec, i, &name, &qtype, &qclass);
if (status != ARES_SUCCESS) {
return status;
}
/* Name */
status = ares__dns_name_write(buf, namelist, ARES_TRUE, name);
if (status != ARES_SUCCESS) {
return status;
}
/* Type */
status = ares__buf_append_be16(buf, (unsigned short)qtype);
if (status != ARES_SUCCESS) {
return status;
}
/* Class */
status = ares__buf_append_be16(buf, (unsigned short)qclass);
if (status != ARES_SUCCESS) {
return status;
}
}
return ARES_SUCCESS;
}
static ares_status_t ares_dns_write_rr_name(ares__buf_t *buf,
const ares_dns_rr_t *rr,
ares__llist_t **namelist,
ares_bool_t validate_hostname,
ares_dns_rr_key_t key)
{
const char *name;
name = ares_dns_rr_get_str(rr, key);
if (name == NULL) {
return ARES_EFORMERR;
}
return ares__dns_name_write(buf, namelist, validate_hostname, name);
}
static ares_status_t ares_dns_write_rr_str(ares__buf_t *buf,
const ares_dns_rr_t *rr,
ares_dns_rr_key_t key)
{
const char *str;
size_t len;
ares_status_t status;
str = ares_dns_rr_get_str(rr, key);
if (str == NULL || (len = ares_strlen(str)) > 255) {
return ARES_EFORMERR;
}
/* Write 1 byte length */
status = ares__buf_append_byte(buf, (unsigned char)(len & 0xFF));
if (status != ARES_SUCCESS) {
return status;
}
if (len == 0) {
return ARES_SUCCESS;
}
/* Write string */
return ares__buf_append(buf, (unsigned char *)str, len);
}
static ares_status_t ares_dns_write_rr_strs(ares__buf_t *buf,
const ares_dns_rr_t *rr,
ares_dns_rr_key_t key)
{
const char *str;
const char *ptr;
ares_status_t status;
str = ares_dns_rr_get_str(rr, key);
if (str == NULL) {
return ARES_EFORMERR;
}
/* split into possible multiple 255-byte or less length strings */
ptr = str;
do {
size_t ptr_len = ares_strlen(ptr);
if (ptr_len > 255) {
ptr_len = 255;
}
/* Length */
status = ares__buf_append_byte(buf, (unsigned char)(ptr_len & 0xFF));
if (status != ARES_SUCCESS) {
return status;
}
/* String */
if (ptr_len) {
status = ares__buf_append(buf, (unsigned char *)ptr, ptr_len);
if (status != ARES_SUCCESS) {
return status;
}
}
ptr += ptr_len;
} while (*ptr != 0);
return ARES_SUCCESS;
}
static ares_status_t ares_dns_write_rr_be32(ares__buf_t *buf,
const ares_dns_rr_t *rr,
ares_dns_rr_key_t key)
{
if (ares_dns_rr_key_datatype(key) != ARES_DATATYPE_U32) {
return ARES_EFORMERR;
}
return ares__buf_append_be32(buf, ares_dns_rr_get_u32(rr, key));
}
static ares_status_t ares_dns_write_rr_be16(ares__buf_t *buf,
const ares_dns_rr_t *rr,
ares_dns_rr_key_t key)
{
if (ares_dns_rr_key_datatype(key) != ARES_DATATYPE_U16) {
return ARES_EFORMERR;
}
return ares__buf_append_be16(buf, ares_dns_rr_get_u16(rr, key));
}
static ares_status_t ares_dns_write_rr_u8(ares__buf_t *buf,
const ares_dns_rr_t *rr,
ares_dns_rr_key_t key)
{
if (ares_dns_rr_key_datatype(key) != ARES_DATATYPE_U8) {
return ARES_EFORMERR;
}
return ares__buf_append_byte(buf, ares_dns_rr_get_u8(rr, key));
}
static ares_status_t ares_dns_write_rr_a(ares__buf_t *buf,
const ares_dns_rr_t *rr,
ares__llist_t **namelist)
{
const struct in_addr *addr;
(void)namelist;
addr = ares_dns_rr_get_addr(rr, ARES_RR_A_ADDR);
if (addr == NULL) {
return ARES_EFORMERR;
}
return ares__buf_append(buf, (unsigned char *)&addr, sizeof(*addr));
}
static ares_status_t ares_dns_write_rr_ns(ares__buf_t *buf,
const ares_dns_rr_t *rr,
ares__llist_t **namelist)
{
return ares_dns_write_rr_name(buf, rr, namelist, ARES_FALSE,
ARES_RR_NS_NSDNAME);
}
static ares_status_t ares_dns_write_rr_cname(ares__buf_t *buf,
const ares_dns_rr_t *rr,
ares__llist_t **namelist)
{
return ares_dns_write_rr_name(buf, rr, namelist, ARES_FALSE,
ARES_RR_CNAME_CNAME);
}
static ares_status_t ares_dns_write_rr_soa(ares__buf_t *buf,
const ares_dns_rr_t *rr,
ares__llist_t **namelist)
{
ares_status_t status;
/* MNAME */
status =
ares_dns_write_rr_name(buf, rr, namelist, ARES_FALSE, ARES_RR_SOA_MNAME);
if (status != ARES_SUCCESS) {
return status;
}
/* RNAME */
status =
ares_dns_write_rr_name(buf, rr, namelist, ARES_FALSE, ARES_RR_SOA_RNAME);
if (status != ARES_SUCCESS) {
return status;
}
/* SERIAL */
status = ares_dns_write_rr_be32(buf, rr, ARES_RR_SOA_SERIAL);
if (status != ARES_SUCCESS) {
return status;
}
/* REFRESH */
status = ares_dns_write_rr_be32(buf, rr, ARES_RR_SOA_REFRESH);
if (status != ARES_SUCCESS) {
return status;
}
/* RETRY */
status = ares_dns_write_rr_be32(buf, rr, ARES_RR_SOA_RETRY);
if (status != ARES_SUCCESS) {
return status;
}
/* EXPIRE */
status = ares_dns_write_rr_be32(buf, rr, ARES_RR_SOA_EXPIRE);
if (status != ARES_SUCCESS) {
return status;
}
/* MINIMUM */
return ares_dns_write_rr_be32(buf, rr, ARES_RR_SOA_MINIMUM);
}
static ares_status_t ares_dns_write_rr_ptr(ares__buf_t *buf,
const ares_dns_rr_t *rr,
ares__llist_t **namelist)
{
return ares_dns_write_rr_name(buf, rr, namelist, ARES_FALSE,
ARES_RR_PTR_DNAME);
}
static ares_status_t ares_dns_write_rr_hinfo(ares__buf_t *buf,
const ares_dns_rr_t *rr,
ares__llist_t **namelist)
{
ares_status_t status;
(void)namelist;
/* CPU */
status = ares_dns_write_rr_str(buf, rr, ARES_RR_HINFO_CPU);
if (status != ARES_SUCCESS) {
return status;
}
/* OS */
return ares_dns_write_rr_str(buf, rr, ARES_RR_HINFO_OS);
}
static ares_status_t ares_dns_write_rr_mx(ares__buf_t *buf,
const ares_dns_rr_t *rr,
ares__llist_t **namelist)
{
ares_status_t status;
/* PREFERENCE */
status = ares_dns_write_rr_be16(buf, rr, ARES_RR_MX_PREFERENCE);
if (status != ARES_SUCCESS) {
return status;
}
/* EXCHANGE */
return ares_dns_write_rr_name(buf, rr, namelist, ARES_FALSE,
ARES_RR_MX_EXCHANGE);
}
static ares_status_t ares_dns_write_rr_txt(ares__buf_t *buf,
const ares_dns_rr_t *rr,
ares__llist_t **namelist)
{
(void)namelist;
return ares_dns_write_rr_strs(buf, rr, ARES_RR_TXT_DATA);
}
static ares_status_t ares_dns_write_rr_aaaa(ares__buf_t *buf,
const ares_dns_rr_t *rr,
ares__llist_t **namelist)
{
const struct ares_in6_addr *addr;
(void)namelist;
addr = ares_dns_rr_get_addr6(rr, ARES_RR_AAAA_ADDR);
if (addr == NULL) {
return ARES_EFORMERR;
}
return ares__buf_append(buf, (unsigned char *)&addr, sizeof(*addr));
}
static ares_status_t ares_dns_write_rr_srv(ares__buf_t *buf,
const ares_dns_rr_t *rr,
ares__llist_t **namelist)
{
ares_status_t status;
/* PRIORITY */
status = ares_dns_write_rr_be16(buf, rr, ARES_RR_SRV_PRIORITY);
if (status != ARES_SUCCESS) {
return status;
}
/* WEIGHT */
status = ares_dns_write_rr_be16(buf, rr, ARES_RR_SRV_WEIGHT);
if (status != ARES_SUCCESS) {
return status;
}
/* PORT */
status = ares_dns_write_rr_be16(buf, rr, ARES_RR_SRV_PORT);
if (status != ARES_SUCCESS) {
return status;
}
/* TARGET */
return ares_dns_write_rr_name(buf, rr, namelist, ARES_FALSE,
ARES_RR_SRV_TARGET);
}
static ares_status_t ares_dns_write_rr_naptr(ares__buf_t *buf,
const ares_dns_rr_t *rr,
ares__llist_t **namelist)
{
ares_status_t status;
/* ORDER */
status = ares_dns_write_rr_be16(buf, rr, ARES_RR_NAPTR_ORDER);
if (status != ARES_SUCCESS) {
return status;
}
/* PREFERENCE */
status = ares_dns_write_rr_be16(buf, rr, ARES_RR_NAPTR_PREFERENCE);
if (status != ARES_SUCCESS) {
return status;
}
/* FLAGS */
status = ares_dns_write_rr_str(buf, rr, ARES_RR_NAPTR_FLAGS);
if (status != ARES_SUCCESS) {
return status;
}
/* SERVICES */
status = ares_dns_write_rr_str(buf, rr, ARES_RR_NAPTR_SERVICES);
if (status != ARES_SUCCESS) {
return status;
}
/* REGEXP */
status = ares_dns_write_rr_str(buf, rr, ARES_RR_NAPTR_REGEXP);
if (status != ARES_SUCCESS) {
return status;
}
/* REPLACEMENT */
return ares_dns_write_rr_name(buf, rr, namelist, ARES_FALSE,
ARES_RR_NAPTR_REPLACEMENT);
}
static ares_status_t ares_dns_write_rr_opt(ares__buf_t *buf,
const ares_dns_rr_t *rr,
ares__llist_t **namelist)
{
size_t len = ares__buf_len(buf);
ares_status_t status;
unsigned int ttl = 0;
(void)namelist;
/* We need to go back and overwrite the class and ttl that were emitted as
* the OPT record overloads them for its own use (yes, very strange!) */
status = ares__buf_set_length(buf, len - 2 /* RDLENGTH */
- 4 /* TTL */
- 2 /* CLASS */);
if (status != ARES_SUCCESS) {
return status;
}
/* Class -> UDP Size */
status = ares_dns_write_rr_be16(buf, rr, ARES_RR_OPT_UDP_SIZE);
if (status != ARES_SUCCESS) {
return status;
}
/* TTL -> rcode (u8) << 24 | version (u8) << 16 | flags (u16) */
ttl |= (unsigned int)ares_dns_rr_get_u8(rr, ARES_RR_OPT_EXT_RCODE) << 24;
ttl |= (unsigned int)ares_dns_rr_get_u8(rr, ARES_RR_OPT_VERSION) << 16;
ttl |= (unsigned int)ares_dns_rr_get_u16(rr, ARES_RR_OPT_FLAGS);
status = ares__buf_append_be32(buf, ttl);
if (status != ARES_SUCCESS) {
return status;
}
/* Now go back to real end */
status = ares__buf_set_length(buf, len);
if (status != ARES_SUCCESS) {
return status;
}
/* TODO: handle additional opt messages here */
return ARES_SUCCESS;
}
static ares_status_t ares_dns_write_rr_uri(ares__buf_t *buf,
const ares_dns_rr_t *rr,
ares__llist_t **namelist)
{
ares_status_t status;
const char *target;
(void)namelist;
/* PRIORITY */
status = ares_dns_write_rr_be16(buf, rr, ARES_RR_URI_PRIORITY);
if (status != ARES_SUCCESS) {
return status;
}
/* WEIGHT */
status = ares_dns_write_rr_be16(buf, rr, ARES_RR_URI_WEIGHT);
if (status != ARES_SUCCESS) {
return status;
}
/* TARGET -- not in DNS string format, rest of buffer, required to be
* non-zero length */
target = ares_dns_rr_get_str(rr, ARES_RR_URI_TARGET);
if (target == NULL || ares_strlen(target) == 0) {
return ARES_EFORMERR;
}
return ares__buf_append(buf, (unsigned char *)target, ares_strlen(target));
}
static ares_status_t ares_dns_write_rr_caa(ares__buf_t *buf,
const ares_dns_rr_t *rr,
ares__llist_t **namelist)
{
const unsigned char *data = NULL;
size_t data_len = 0;
ares_status_t status;
(void)namelist;
/* CRITICAL */
status = ares_dns_write_rr_u8(buf, rr, ARES_RR_CAA_CRITICAL);
if (status != ARES_SUCCESS) {
return status;
}
/* Tag */
status = ares_dns_write_rr_str(buf, rr, ARES_RR_CAA_TAG);
if (status != ARES_SUCCESS) {
return status;
}
/* Value - binary! (remaining buffer */
data = ares_dns_rr_get_bin(rr, ARES_RR_CAA_VALUE, &data_len);
if (data == NULL || data_len == 0) {
return ARES_EFORMERR;
}
return ares__buf_append(buf, data, data_len);
}
static ares_status_t ares_dns_write_rr_raw_rr(ares__buf_t *buf,
const ares_dns_rr_t *rr,
ares__llist_t **namelist)
{
size_t len = ares__buf_len(buf);
ares_status_t status;
const unsigned char *data = NULL;
size_t data_len = 0;
(void)namelist;
/* We need to go back and overwrite the type that was emitted by the parent
* function */
status = ares__buf_set_length(buf, len - 2 /* RDLENGTH */
- 4 /* TTL */
- 2 /* CLASS */
- 2 /* TYPE */);
if (status != ARES_SUCCESS) {
return status;
}
status = ares_dns_write_rr_be16(buf, rr, ARES_RR_RAW_RR_TYPE);
if (status != ARES_SUCCESS) {
return status;
}
/* Now go back to real end */
status = ares__buf_set_length(buf, len);
if (status != ARES_SUCCESS) {
return status;
}
/* Output raw data */
data = ares_dns_rr_get_bin(rr, ARES_RR_RAW_RR_DATA, &data_len);
if (data == NULL) {
return ARES_EFORMERR;
}
if (data_len == 0) {
return ARES_SUCCESS;
}
return ares__buf_append(buf, data, data_len);
}
static ares_status_t ares_dns_write_rr(ares_dns_record_t *dnsrec,
ares__llist_t **namelist,
ares_dns_section_t section,
ares__buf_t *buf)
{
size_t i;
for (i = 0; i < ares_dns_record_rr_cnt(dnsrec, section); i++) {
ares_dns_rr_t *rr;
ares_dns_rec_type_t type;
ares_bool_t allow_compress;
ares__llist_t **namelistptr = NULL;
size_t pos_len;
ares_status_t status;
size_t rdlength;
size_t end_length;
rr = ares_dns_record_rr_get(dnsrec, section, i);
if (rr == NULL) {
return ARES_EFORMERR;
}
type = ares_dns_rr_get_type(rr);
allow_compress = ares_dns_rec_type_allow_name_compression(type);
if (allow_compress) {
namelistptr = namelist;
}
/* Name */
status =
ares__dns_name_write(buf, namelist, ARES_TRUE, ares_dns_rr_get_name(rr));
if (status != ARES_SUCCESS) {
return status;
}
/* Type */
status = ares__buf_append_be16(buf, (unsigned short)type);
if (status != ARES_SUCCESS) {
return status;
}
/* Class */
status =
ares__buf_append_be16(buf, (unsigned short)ares_dns_rr_get_class(rr));
if (status != ARES_SUCCESS) {
return status;
}
/* TTL */
status = ares__buf_append_be32(buf, ares_dns_rr_get_ttl(rr));
if (status != ARES_SUCCESS) {
return status;
}
/* Length */
pos_len = ares__buf_len(buf); /* Save to write real length later */
status = ares__buf_append_be16(buf, 0);
if (status != ARES_SUCCESS) {
return status;
}
/* Data */
switch (type) {
case ARES_REC_TYPE_A:
status = ares_dns_write_rr_a(buf, rr, namelistptr);
break;
case ARES_REC_TYPE_NS:
status = ares_dns_write_rr_ns(buf, rr, namelistptr);
break;
case ARES_REC_TYPE_CNAME:
status = ares_dns_write_rr_cname(buf, rr, namelistptr);
break;
case ARES_REC_TYPE_SOA:
status = ares_dns_write_rr_soa(buf, rr, namelistptr);
break;
case ARES_REC_TYPE_PTR:
status = ares_dns_write_rr_ptr(buf, rr, namelistptr);
break;
case ARES_REC_TYPE_HINFO:
status = ares_dns_write_rr_hinfo(buf, rr, namelistptr);
break;
case ARES_REC_TYPE_MX:
status = ares_dns_write_rr_mx(buf, rr, namelistptr);
break;
case ARES_REC_TYPE_TXT:
status = ares_dns_write_rr_txt(buf, rr, namelistptr);
break;
case ARES_REC_TYPE_AAAA:
status = ares_dns_write_rr_aaaa(buf, rr, namelistptr);
break;
case ARES_REC_TYPE_SRV:
status = ares_dns_write_rr_srv(buf, rr, namelistptr);
break;
case ARES_REC_TYPE_NAPTR:
status = ares_dns_write_rr_naptr(buf, rr, namelistptr);
break;
case ARES_REC_TYPE_ANY:
status = ARES_EFORMERR;
break;
case ARES_REC_TYPE_OPT:
status = ares_dns_write_rr_opt(buf, rr, namelistptr);
break;
case ARES_REC_TYPE_URI:
status = ares_dns_write_rr_uri(buf, rr, namelistptr);
break;
case ARES_REC_TYPE_CAA:
status = ares_dns_write_rr_caa(buf, rr, namelistptr);
break;
case ARES_REC_TYPE_RAW_RR:
status = ares_dns_write_rr_raw_rr(buf, rr, namelistptr);
break;
}
if (status != ARES_SUCCESS) {
return status;
}
/* Back off write pointer, write real length, then go back to proper
* position */
end_length = ares__buf_len(buf);
rdlength = end_length - pos_len - 2;
status = ares__buf_set_length(buf, pos_len);
if (status != ARES_SUCCESS) {
return status;
}
status = ares__buf_append_be16(buf, (unsigned short)(rdlength & 0xFFFF));
if (status != ARES_SUCCESS) {
return status;
}
status = ares__buf_set_length(buf, end_length);
if (status != ARES_SUCCESS) {
return status;
}
}
return ARES_SUCCESS;
}
ares_status_t ares_dns_write(ares_dns_record_t *dnsrec, unsigned char **buf,
size_t *buf_len)
{
ares__buf_t *b = NULL;
ares_status_t status;
ares__llist_t *namelist = NULL;
if (buf == NULL || buf_len == NULL || dnsrec == NULL) {
return ARES_EFORMERR;
}
*buf = NULL;
*buf_len = 0;
b = ares__buf_create();
if (b == NULL) {
return ARES_ENOMEM;
}
status = ares_dns_write_header(dnsrec, b);
if (status != ARES_SUCCESS) {
goto done;
}
status = ares_dns_write_questions(dnsrec, &namelist, b);
if (status != ARES_SUCCESS) {
goto done;
}
status = ares_dns_write_rr(dnsrec, &namelist, ARES_SECTION_ANSWER, b);
if (status != ARES_SUCCESS) {
goto done;
}
status = ares_dns_write_rr(dnsrec, &namelist, ARES_SECTION_AUTHORITY, b);
if (status != ARES_SUCCESS) {
goto done;
}
status = ares_dns_write_rr(dnsrec, &namelist, ARES_SECTION_ADDITIONAL, b);
if (status != ARES_SUCCESS) {
goto done;
}
done:
ares__llist_destroy(namelist);
if (status != ARES_SUCCESS) {
ares__buf_destroy(b);
return status;
}
*buf = ares__buf_finish_bin(b, buf_len);
return status;
}

@ -72,7 +72,7 @@ ares_status_t ares__expand_name_validated(const unsigned char *encoded,
}
start_len = ares__buf_len(buf);
status = ares__buf_parse_dns_name(buf, s, is_hostname);
status = ares__dns_name_parse(buf, s, is_hostname);
if (status != ARES_SUCCESS) {
goto done;
}

@ -215,13 +215,10 @@ struct query {
/* connection handle query is associated with */
struct server_connection *conn;
/* Query buf with length at beginning, for TCP transmission */
unsigned char *tcpbuf;
size_t tcplen;
/* Arguments passed to ares_send() (qbuf points into tcpbuf) */
const unsigned char *qbuf;
/* Arguments passed to ares_send() */
unsigned char *qbuf;
size_t qlen;
ares_callback callback;
void *arg;
@ -500,6 +497,46 @@ ares_status_t ares__hosts_entry_to_addrinfo(const ares_hosts_entry_t *entry,
unsigned short port,
ares_bool_t want_cnames,
struct ares_addrinfo *ai);
ares_bool_t ares__isprint(int ch);
/*! Parse a compressed DNS name as defined in RFC1035 starting at the current
* offset within the buffer.
*
* It is assumed that either a const buffer is being used, or before
* the message processing was started that ares__buf_reclaim() was called.
*
* \param[in] buf Initialized buffer object
* \param[out] name Pointer passed by reference to be filled in with
* allocated string of the parsed name that must be
* ares_free()'d by the caller.
* \param[in] is_hostname if ARES_TRUE, will validate the character set for
* a valid hostname or will return error.
* \return ARES_SUCCESS on success
*/
ares_status_t ares__dns_name_parse(ares__buf_t *buf, char **name,
ares_bool_t is_hostname);
/*! Write the DNS name to the buffer in the DNS domain-name syntax as a
* series of labels. The maximum domain name length is 255 characters with
* each label being a maximum of 63 characters. If the validate_hostname
* flag is set, it will strictly validate the character set.
*
* \param[in,out] buf Initialized buffer object to write name to
* \param[in,out] list Pointer passed by reference to maintain a list of
* domain name to indexes used for name compression.
* Pass NULL (not by reference) if name compression isn't
* desired. Otherwise the list will be automatically
* created upon first entry.
* \param[in] validate_hostname Validate the hostname character set.
* \param[in] name Name to write out, it may have escape
* sequences.
* \return ARES_SUCCESS on success, most likely ARES_EBADNAME if the name is
* bad.
*/
ares_status_t ares__dns_name_write(ares__buf_t *buf, ares__llist_t **list,
ares_bool_t validate_hostname,
const char *name);
#define ARES_SWAP_BYTE(a, b) \
do { \

@ -60,16 +60,16 @@ static ares_status_t process_answer(ares_channel_t *channel,
static void handle_conn_error(struct server_connection *conn,
ares_bool_t critical_failure);
static ares_bool_t same_questions(const unsigned char *qbuf, size_t qlen,
static ares_bool_t same_questions(const ares_dns_record_t *qrec,
const ares_dns_record_t *arec);
static ares_bool_t same_address(const struct sockaddr *sa,
const struct ares_addr *aa);
static ares_bool_t has_opt_rr(ares_dns_record_t *arec);
static void end_query(const ares_channel_t *channel,
struct query *query, ares_status_t status,
const unsigned char *abuf, size_t alen);
static void end_query(const ares_channel_t *channel, struct query *query,
ares_status_t status, const unsigned char *abuf,
size_t alen);
static void server_increment_failures(struct server_state *server)
static void server_increment_failures(struct server_state *server)
{
ares__slist_node_t *node;
const ares_channel_t *channel = server->channel;
@ -357,7 +357,7 @@ static int socket_list_append(ares_socket_t **socketlist, ares_socket_t fd,
}
static ares_socket_t *channel_socket_list(const ares_channel_t *channel,
size_t *num)
size_t *num)
{
size_t alloc_cnt = 1 << 4;
ares_socket_t *out = ares_malloc(alloc_cnt * sizeof(*out));
@ -556,6 +556,46 @@ static void process_timeouts(ares_channel_t *channel, struct timeval *now)
}
}
static ares_status_t rewrite_without_edns(ares_dns_record_t *qdnsrec,
struct query *query)
{
ares_status_t status;
size_t i;
ares_bool_t found_opt_rr = ARES_FALSE;
unsigned char *msg = NULL;
size_t msglen = 0;
/* Find and remove the OPT RR record */
for (i = 0; i < ares_dns_record_rr_cnt(qdnsrec, ARES_SECTION_ADDITIONAL);
i++) {
ares_dns_rr_t *rr;
rr = ares_dns_record_rr_get(qdnsrec, ARES_SECTION_ADDITIONAL, i);
if (ares_dns_rr_get_type(rr) == ARES_REC_TYPE_OPT) {
ares_dns_record_rr_del(qdnsrec, ARES_SECTION_ADDITIONAL, i);
found_opt_rr = ARES_TRUE;
break;
}
}
if (!found_opt_rr) {
status = ARES_EFORMERR;
goto done;
}
/* Rewrite the DNS message */
status = ares_dns_write(qdnsrec, &msg, &msglen);
if (status != ARES_SUCCESS) {
goto done;
}
ares_free(query->qbuf);
query->qbuf = msg;
query->qlen = msglen;
done:
return status;
}
/* Handle an answer from a server. This must NEVER cleanup the
* server connection! Return something other than ARES_SUCCESS to cause
* the connection to be terminated after this call. */
@ -564,16 +604,16 @@ static ares_status_t process_answer(ares_channel_t *channel,
struct server_connection *conn,
ares_bool_t tcp, struct timeval *now)
{
size_t packetsz;
struct query *query;
/* Cache these as once ares__send_query() gets called, it may end up
* invalidating the connection all-together */
struct server_state *server = conn->server;
ares_dns_record_t *dnsrec = NULL;
struct server_state *server = conn->server;
ares_dns_record_t *rdnsrec = NULL;
ares_dns_record_t *qdnsrec = NULL;
ares_status_t status;
/* Parse the response */
status = ares_dns_parse(abuf, alen, 0, &dnsrec);
status = ares_dns_parse(abuf, alen, 0, &rdnsrec);
if (status != ARES_SUCCESS) {
/* Malformations are never accepted */
status = ARES_EBADRESP;
@ -584,16 +624,23 @@ static ares_status_t process_answer(ares_channel_t *channel,
* hashed/bucketed by query id, so this lookup should be quick.
*/
query = ares__htable_szvp_get_direct(channel->queries_by_qid,
ares_dns_record_get_id(dnsrec));
ares_dns_record_get_id(rdnsrec));
if (!query) {
/* We may have stopped listening for this query, that's ok */
status = ARES_SUCCESS;
goto cleanup;
}
/* Parse the question we sent as we use it to compare */
status = ares_dns_parse(query->qbuf, query->qlen, 0, &qdnsrec);
if (status != ARES_SUCCESS) {
end_query(channel, query, status, NULL, 0);
goto cleanup;
}
/* Both the query id and the questions must be the same. We will drop any
* replies that aren't for the same query as this is considered invalid. */
if (!same_questions(query->qbuf, query->qlen, dnsrec)) {
if (!same_questions(qdnsrec, rdnsrec)) {
/* Possible qid conflict due to delayed response, that's ok */
status = ARES_SUCCESS;
goto cleanup;
@ -606,35 +653,28 @@ static ares_status_t process_answer(ares_channel_t *channel,
ares__llist_node_destroy(query->node_queries_to_conn);
query->node_queries_to_conn = NULL;
packetsz = PACKETSZ;
/* If we use EDNS and server answers with FORMERR without an OPT RR, the
* protocol extension is not understood by the responder. We must retry the
* query without EDNS enabled. */
if (channel->flags & ARES_FLAG_EDNS) {
packetsz = channel->ednspsz;
if (ares_dns_record_get_rcode(dnsrec) == ARES_RCODE_FORMAT_ERROR &&
!has_opt_rr(dnsrec)) {
size_t qlen = (query->tcplen - 2) - EDNSFIXEDSZ;
channel->flags ^= ARES_FLAG_EDNS;
query->tcplen -= EDNSFIXEDSZ;
query->qlen -= EDNSFIXEDSZ;
query->tcpbuf[0] = (unsigned char)((qlen >> 8) & 0xff);
query->tcpbuf[1] = (unsigned char)(qlen & 0xff);
DNS_HEADER_SET_ARCOUNT(query->tcpbuf + 2, 0);
query->tcpbuf = ares_realloc(query->tcpbuf, query->tcplen);
query->qbuf = query->tcpbuf + 2;
ares__send_query(query, now);
status = ARES_SUCCESS;
if (ares_dns_record_get_rcode(rdnsrec) == ARES_RCODE_FORMAT_ERROR &&
has_opt_rr(qdnsrec) && !has_opt_rr(rdnsrec)) {
status = rewrite_without_edns(qdnsrec, query);
if (status != ARES_SUCCESS) {
end_query(channel, query, status, NULL, 0);
goto cleanup;
}
ares__send_query(query, now);
status = ARES_SUCCESS;
goto cleanup;
}
/* If we got a truncated UDP packet and are not ignoring truncation,
* don't accept the packet, and switch the query to TCP if we hadn't
* done so already.
*/
if ((ares_dns_record_get_flags(dnsrec) & ARES_FLAG_TC || alen > packetsz) &&
!tcp && !(channel->flags & ARES_FLAG_IGNTC)) {
if (ares_dns_record_get_flags(rdnsrec) & ARES_FLAG_TC && !tcp &&
!(channel->flags & ARES_FLAG_IGNTC)) {
if (!query->using_tcp) {
query->using_tcp = ARES_TRUE;
ares__send_query(query, now);
@ -647,7 +687,7 @@ static ares_status_t process_answer(ares_channel_t *channel,
* with SERVFAIL, NOTIMP, or REFUSED response codes.
*/
if (!(channel->flags & ARES_FLAG_NOCHECKRESP)) {
ares_dns_rcode_t rcode = ares_dns_record_get_rcode(dnsrec);
ares_dns_rcode_t rcode = ares_dns_record_get_rcode(rdnsrec);
if (rcode == ARES_RCODE_SERVER_FAILURE ||
rcode == ARES_RCODE_NOT_IMPLEMENTED || rcode == ARES_RCODE_REFUSED) {
switch (rcode) {
@ -679,7 +719,8 @@ static ares_status_t process_answer(ares_channel_t *channel,
status = ARES_SUCCESS;
cleanup:
ares_dns_record_destroy(dnsrec);
ares_dns_record_destroy(rdnsrec);
ares_dns_record_destroy(qdnsrec);
return status;
}
@ -744,6 +785,18 @@ static struct server_state *ares__random_server(ares_channel_t *channel)
return NULL;
}
static ares_status_t ares__append_tcpbuf(struct server_state *server,
struct query *query)
{
ares_status_t status;
status = ares__buf_append_be16(server->tcp_send, (unsigned short)query->qlen);
if (status != ARES_SUCCESS) {
return status;
}
return ares__buf_append(server->tcp_send, query->qbuf, query->qlen);
}
ares_status_t ares__send_query(struct query *query, struct timeval *now)
{
ares_channel_t *channel = query->channel;
@ -795,7 +848,7 @@ ares_status_t ares__send_query(struct query *query, struct timeval *now)
prior_len = ares__buf_len(server->tcp_send);
status = ares__buf_append(server->tcp_send, query->tcpbuf, query->tcplen);
status = ares__append_tcpbuf(server, query);
if (status != ARES_SUCCESS) {
end_query(channel, query, status, NULL, 0);
@ -930,16 +983,12 @@ ares_status_t ares__send_query(struct query *query, struct timeval *now)
return ARES_SUCCESS;
}
static ares_bool_t same_questions(const unsigned char *qbuf, size_t qlen,
static ares_bool_t same_questions(const ares_dns_record_t *qrec,
const ares_dns_record_t *arec)
{
ares_dns_record_t *qrec = NULL;
size_t i;
ares_bool_t rv = ARES_FALSE;
size_t i;
ares_bool_t rv = ARES_FALSE;
if (ares_dns_parse(qbuf, qlen, 0, &qrec) != ARES_SUCCESS) {
goto done;
}
if (ares_dns_record_query_cnt(qrec) != ares_dns_record_query_cnt(arec)) {
goto done;
@ -972,7 +1021,6 @@ static ares_bool_t same_questions(const unsigned char *qbuf, size_t qlen,
rv = ARES_TRUE;
done:
ares_dns_record_destroy(qrec);
return rv;
}
@ -1053,7 +1101,7 @@ void ares__free_query(struct query *query)
query->callback = NULL;
query->arg = NULL;
/* Deallocate the memory associated with the query */
ares_free(query->tcpbuf);
ares_free(query->qbuf);
ares_free(query);
}

@ -62,8 +62,8 @@ ares_status_t ares_send_ex(ares_channel_t *channel, const unsigned char *qbuf,
memset(query, 0, sizeof(*query));
query->channel = channel;
query->tcpbuf = ares_malloc(qlen + 2);
if (!query->tcpbuf) {
query->qbuf = ares_malloc(qlen);
if (!query->qbuf) {
ares_free(query);
callback(arg, ARES_ENOMEM, 0, NULL, 0);
return ARES_ENOMEM;
@ -74,17 +74,10 @@ ares_status_t ares_send_ex(ares_channel_t *channel, const unsigned char *qbuf,
query->timeout.tv_sec = 0;
query->timeout.tv_usec = 0;
/* Form the TCP query buffer by prepending qlen (as two
* network-order bytes) to qbuf.
*/
query->tcpbuf[0] = (unsigned char)((qlen >> 8) & 0xff);
query->tcpbuf[1] = (unsigned char)(qlen & 0xff);
memcpy(query->tcpbuf + 2, qbuf, qlen);
query->tcplen = qlen + 2;
memcpy(query->qbuf, qbuf, qlen);
query->qlen = qlen;
/* Fill in query arguments. */
query->qbuf = query->tcpbuf + 2;
query->qlen = qlen;
query->callback = callback;
query->arg = arg;

@ -504,7 +504,7 @@ TEST_F(LibraryTest, BufMisuse) {
EXPECT_NE(ARES_SUCCESS, ares__buf_begins_with(NULL, NULL, 0));
EXPECT_EQ(0, ares__buf_get_position(NULL));
EXPECT_NE(ARES_SUCCESS, ares__buf_set_position(NULL, 0));
EXPECT_NE(ARES_SUCCESS, ares__buf_parse_dns_name(NULL, NULL, ARES_FALSE));
EXPECT_NE(ARES_SUCCESS, ares__dns_name_parse(NULL, NULL, ARES_FALSE));
EXPECT_NE(ARES_SUCCESS, ares__buf_parse_dns_binstr(NULL, 0, NULL, NULL, ARES_FALSE));
}

@ -188,15 +188,17 @@ TEST_F(LibraryTest, Mkquery) {
TEST_F(LibraryTest, CreateQuery) {
byte* p;
int len;
// This is hard to really test with escaping since DNS names don't allow
// bad characters. So we'll escape good characters.
EXPECT_EQ(ARES_SUCCESS,
ares_create_query("exam\\@le.com", C_IN, T_A, 0x1234, 0,
ares_create_query("ex\\097m\\ple.com", C_IN, T_A, 0x1234, 0,
&p, &len, 0));
std::vector<byte> data(p, p + len);
ares_free_string(p);
std::string actual = PacketToString(data);
DNSPacket pkt;
pkt.set_qid(0x1234).add_question(new DNSQuestion("exam@le.com", T_A));
pkt.set_qid(0x1234).add_question(new DNSQuestion("example.com", T_A));
std::string expected = PacketToString(pkt.data());
EXPECT_EQ(expected, actual);
}

Loading…
Cancel
Save