mirror of https://github.com/c-ares/c-ares.git
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
parent
18396b3c25
commit
784ee5a754
16 changed files with 1905 additions and 485 deletions
@ -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; |
||||
} |
@ -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; |
||||
} |
Loading…
Reference in new issue