Add DNS cookie support (RFC7873 + RFC9018) (#833)

DNS cookies are a simple form of learned mutual authentication supported
by most DNS server implementations these days and can help prevent DNS
Cache Poisoning attacks for clients and DNS amplification attacks for
servers.

Fixes #620
Fix By: Brad House (@bradh352)
pull/834/head
Brad House 4 months ago committed by GitHub
parent d4282d7665
commit 4bedfd0d55
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      .github/workflows/msys2.yml
  2. 18
      docs/ares_dns_rr.3
  3. 3
      docs/ares_dns_rr_del_opt_byid.3
  4. 11
      include/ares_dns_record.h
  5. 1
      src/lib/Makefile.inc
  6. 2
      src/lib/ares__close_sockets.c
  7. 75
      src/lib/ares__socket.c
  8. 457
      src/lib/ares_cookie.c
  9. 33
      src/lib/ares_dns_private.h
  10. 65
      src/lib/ares_dns_record.c
  11. 2
      src/lib/ares_dns_write.c
  12. 55
      src/lib/ares_private.h
  13. 64
      src/lib/ares_process.c
  14. 20
      src/lib/ares_sysconfig_mac.c
  15. 6
      src/lib/event/ares_event_thread.c
  16. 19
      src/tools/adig.c
  17. 2
      test/ares-test-misc.cc
  18. 262
      test/ares-test-mock.cc
  19. 4
      test/ares-test-parse-ptr.cc
  20. 55
      test/ares-test.cc
  21. 51
      test/ares-test.h
  22. 101
      test/dns-proto.cc
  23. 43
      test/dns-proto.h

@ -10,7 +10,7 @@ concurrency:
cancel-in-progress: true
env:
TEST_FILTER: "--gtest_filter=-*LiveSearchTXT*:*LiveSearchANY*:*LiveGetLocalhostByAddr*"
TEST_FILTER: "-4 --gtest_filter=-*LiveSearchTXT*:*LiveSearchANY*:*LiveGetLocalhostByAddr*"
CMAKE_FLAGS: "-DCMAKE_BUILD_TYPE=DEBUG -DCARES_STATIC=ON -DCMAKE_INSTALL_PREFIX=C:/projects/build-cares/test_install -DCARES_STATIC_PIC=ON -G Ninja"
CONFIG_OPTS: "--disable-shared"
MAKE: make

@ -96,6 +96,10 @@ ares_status_t ares_dns_rr_set_opt(ares_dns_rr_t *dns_rr,
const unsigned char *val,
size_t val_len);
ares_status_t ares_dns_rr_del_opt_byid(ares_dns_rr_t *dns_rr,
ares_dns_rr_key_t key,
unsigned short opt);
const struct in_addr *ares_dns_rr_get_addr(const ares_dns_rr_t *dns_rr,
ares_dns_rr_key_t key);
@ -553,7 +557,7 @@ parameter, and the index to remove is provided in the
parameter.
The \fIares_dns_rr_set_opt(3)\fP function is used to set option/parameter keys and
values for the resource record when the datatype if \fIARES_DATATYPE_OPT\fP. The
values for the resource record when the datatype is \fIARES_DATATYPE_OPT\fP. The
resource record to be modified is provided in the
.IR dns_rr
parameter. They key/parameter is provided in the
@ -568,6 +572,18 @@ enumerations. The value for the option is always provided in binary form in
with length provided in
.IR val_len.
The \fIares_dns_rr_del_opt_byid(3)\fP function is used to delete option/parameter
keys and values for the resource record when the datatype is
\fIARES_DATATYPE_OPT\fP. The resource record to be modified is provided in the
.IR dns_rr
parameter. They key/parameter is provided in the
.IR key
parameter. The option/parameter value specific to the resource record is provided
in the
.IR opt
parameter. This function returns \fIARES_SUCCESS\fP if the record is successfully
removed, or \fIARES_ENOTFOUND\fP if the record could not be found.
The \fIares_dns_rr_get_addr(3)\fP function is used to retrieve the IPv4 address
from the resource record when the datatype is \fIARES_DATATYPE_INADDR\fP. The
resource record is provided in the

@ -0,0 +1,3 @@
.\" Copyright (C) 2023 The c-ares project and its contributors.
.\" SPDX-License-Identifier: MIT
.so man3/ares_dns_rr.3

@ -918,6 +918,17 @@ CARES_EXTERN ares_status_t ares_dns_rr_set_opt(ares_dns_rr_t *dns_rr,
const unsigned char *val,
size_t val_len);
/*! Delete the option for the RR by id
*
* \param[in] dns_rr Pointer to resource record
* \param[in] key DNS Resource Record Key
* \param[in] opt Option record key id.
* \return ARES_SUCCESS if removed, ARES_ENOTFOUND if not found
*/
CARES_EXTERN ares_status_t ares_dns_rr_del_opt_byid(ares_dns_rr_t *dns_rr,
ares_dns_rr_key_t key,
unsigned short opt);
/*! Retrieve a pointer to the ipv4 address. Can only be used on keys with
* datatype ARES_DATATYPE_INADDR.
*

@ -10,6 +10,7 @@ CSOURCES = ares__addrinfo2hostent.c \
ares__sortaddrinfo.c \
ares_android.c \
ares_cancel.c \
ares_cookie.c \
ares_data.c \
ares_destroy.c \
ares_dns_mapping.c \

@ -37,7 +37,7 @@ static void ares__requeue_queries(struct server_connection *conn,
ares__tvnow(&now);
while ((query = ares__llist_first_val(conn->queries_to_conn)) != NULL) {
ares__requeue_query(query, &now, requeue_status);
ares__requeue_query(query, &now, requeue_status, ARES_TRUE);
}
}

@ -242,6 +242,70 @@ static int configure_socket(ares_socket_t s, struct server_state *server)
return 0;
}
ares_bool_t ares_sockaddr_to_ares_addr(struct ares_addr *ares_addr,
unsigned short *port,
const struct sockaddr *sockaddr)
{
if (sockaddr->sa_family == AF_INET) {
/* NOTE: memcpy sockaddr_in due to alignment issues found by UBSAN due to
* dnsinfo packing on MacOS */
struct sockaddr_in sockaddr_in;
memcpy(&sockaddr_in, sockaddr, sizeof(sockaddr_in));
ares_addr->family = AF_INET;
memcpy(&ares_addr->addr.addr4, &(sockaddr_in.sin_addr),
sizeof(ares_addr->addr.addr4));
if (port) {
*port = ntohs(sockaddr_in.sin_port);
}
return ARES_TRUE;
}
if (sockaddr->sa_family == AF_INET6) {
/* NOTE: memcpy sockaddr_in6 due to alignment issues found by UBSAN due to
* dnsinfo packing on MacOS */
struct sockaddr_in6 sockaddr_in6;
memcpy(&sockaddr_in6, sockaddr, sizeof(sockaddr_in6));
ares_addr->family = AF_INET6;
memcpy(&ares_addr->addr.addr6, &(sockaddr_in6.sin6_addr),
sizeof(ares_addr->addr.addr6));
if (port) {
*port = ntohs(sockaddr_in6.sin6_port);
}
return ARES_TRUE;
}
return ARES_FALSE;
}
static ares_status_t ares_conn_set_self_ip(struct server_connection *conn)
{
/* Some old systems might not have sockaddr_storage, so we make a union
* that's guaranteed to be large enough */
union {
struct sockaddr sa;
struct sockaddr_in sa4;
struct sockaddr_in6 sa6;
} from;
int rv;
ares_socklen_t len = sizeof(from);
memset(&from, 0, sizeof(from));
rv = getsockname(conn->fd, &from.sa, &len);
if (rv != 0) {
return ARES_ECONNREFUSED;
}
if (!ares_sockaddr_to_ares_addr(&conn->self_ip, NULL, &from.sa)) {
return ARES_ECONNREFUSED;
}
return ARES_SUCCESS;
}
ares_status_t ares__open_connection(ares_channel_t *channel,
struct server_state *server,
ares_bool_t is_tcp)
@ -249,6 +313,7 @@ ares_status_t ares__open_connection(ares_channel_t *channel,
ares_socket_t s;
int opt;
ares_socklen_t salen;
ares_status_t status;
union {
struct sockaddr_in sa4;
@ -359,6 +424,16 @@ ares_status_t ares__open_connection(ares_channel_t *channel,
/* LCOV_EXCL_STOP */
}
/* Need to store our own ip for DNS cookie support */
status = ares_conn_set_self_ip(conn);
if (status != ARES_SUCCESS) {
/* LCOV_EXCL_START: UntestablePath */
ares__close_socket(channel, s);
ares_free(conn);
return status;
/* LCOV_EXCL_STOP */
}
/* TCP connections are thrown to the end as we don't spawn multiple TCP
* connections. UDP connections are put on front where the newest connection
* can be quickly pulled */

@ -0,0 +1,457 @@
/* MIT License
*
* Copyright (c) 2024 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
*/
/* DNS cookies are a simple form of learned mutual authentication supported by
* most DNS server implementations these days and can help prevent DNS Cache
* Poisoning attacks for clients and DNS amplification attacks for servers.
*
* A good overview is here:
* https://www.dotmagazine.online/issues/digital-responsibility-and-sustainability/dns-cookies-transaction-mechanism
*
* RFCs used for implementation are
* [RFC7873](https://datatracker.ietf.org/doc/html/rfc7873) which is extended by
* [RFC9018](https://datatracker.ietf.org/doc/html/rfc9018).
*
* Though this could be used on TCP, the likelihood of it being useful is small
* and could cause some issues. TCP is better used as a fallback in case there
* are issues with DNS Cookie support in the upstream servers (e.g. AnyCast
* cluster issues).
*
* While most recursive DNS servers support DNS Cookies, public DNS servers like
* Google (8.8.8.8, 8.8.4.4) and CloudFlare (1.1.1.1, 1.0.0.1) don't seem to
* have this enabled yet for unknown reasons.
*
* The risk to having DNS Cookie support always enabled is nearly zero as there
* is built-in detection support and it will simply bypass using cookies if the
* remote server doesn't support it. The problem arises if a remote server
* supports DNS cookies, then stops supporting them (such as if an administrator
* reconfigured the server, or maybe there are different servers in a cluster
* with different configurations). We need to detect this behavior by tracking
* how much time has gone by since we received our last valid cookie reply, and
* if we exceed the threshold, reset all cookie parameters like we haven't
* attempted a request yet.
*
* ## Implementation Plan
*
* ### Constants:
* - `COOKIE_CLIENT_TIMEOUT`: 86400s (1 day)
* - How often to regenerate the per-server client cookie, even if our
* source ip address hasn't changed.
* - `COOKIE_UNSUPPORTED_TIMEOUT`: 300s (5 minutes)
* - If a server responds without a cookie in the reply, this is how long to
* wait before attempting to send a client cookie again.
* - `COOKIE_REGRESSION_TIMEOUT`: 120s (2 minutes)
* - If a server was once known to return cookies, and all of a sudden stops
* returning cookies (but the reply is otherwise valid), this is how long
* to continue to attempt to use cookies before giving up and resetting.
* Such an event would cause an outage for this duration, but since a
* cache poisoning attack should be dropping invalid replies we should be
* able to still get the valid reply and not assume it is a server
* regression just because we received replies without cookies.
* - `COOKIE_RESEND_MAX`: 3
* - Maximum times to resend a query to a server due to the server responding
* with `BAD_COOKIE`, after this, we switch to TCP.
*
* ### Per-server variables:
* - `cookie.state`: Known state of cookie support, enumeration.
* - `INITIAL` (0): Initial state, not yet determined. Used during startup.
* - `GENERATED` (1): Cookie has been generated and sent to a server, but no
* validated response yet.
* - `SUPPORTED` (2): Server has been determined to properly support cookies
* - `UNSUPPORTED` (3): Server has been determined to not support cookies
* - `cookie.client` : 8 byte randomly generated client cookie
* - `cookie.client_ts`: Timestamp client cookie was generated
* - `cookie.client_ip`: IP address client used to connect to server
* - `cookie.server`: 8 to 32 byte server cookie
* - `cookie.server_len`: length of server cookie
* - `cookie.unsupported_ts`: Timestamp of last attempt to use a cookies, but
* it was determined that the server didn't support them.
*
* ### Per-query variables:
* - `query.client_cookie`: Duplicate of `cookie.client` at the point in time
* the query is put on the wire. This should be available in the
* `ares_dns_record_t` for the request for verification purposes so we don't
* actually need to duplicate this, just naming it here for the ease of
* documentation below.
* - `query.cookie_try_count`: Number of tries to send a cookie but receive
* `BAD_COOKIE` responses. Used to know when we need to switch to TCP.
*
* ### Procedure:
* **NOTE**: These steps will all be done after obtaining a connection handle as
* some of these steps depend on determining the source ip address for the
* connection.
*
* 1. If the query is not using EDNS, then **skip any remaining processing**.
* 2. If using TCP, ensure there is no EDNS cookie opt (10) set (there may have
* been if this is a resend after upgrade to TCP), then **skip any remaining
* processing**.
* 3. If `cookie.state == SUPPORTED`, `cookie.unsupported_ts` is non-zero, and
* evaluates greater than `COOKIE_REGRESSION_TIMEOUT`, then clear all cookie
* settings, set `cookie.state = INITIAL`. Continue to next step (4)
* 4. If `cookie.state == UNSUPPORTED`
* - If `cookie.unsupported_ts` evaluates less than
* `COOKIE_UNSUPPORTED_TIMEOUT`
* - Ensure there is no EDNS cookie opt (10) set (shouldn't be unless
* requestor had put this themselves), then **skip any remaining
* processing** as we don't want to try to send cookies.
* - Otherwise:
* - clear all cookie settings, set `cookie.state = INITIAL`.
* - Continue to next step (5) which will send a new cookie.
* 5. If `cookie.state == INITIAL`:
* - randomly generate new `cookie.client`
* - set `cookie.client_ts` to the current timestamp.
* - set `cookie.state = GENERATED`.
* - set `cookie.client_ip` to the current source ip address.
* 6. If `cookie.state == GENERATED || cookie.state == SUPPORTED` and
* `cookie.client_ip` does not match the current source ip address:
* - clear `cookie.server`
* - randomly generate new `cookie.client`
* - set `cookie.client_ts` to the current timestamp.
* - set `cookie.client_ip` to the current source ip address.
* - do not change the `cookie.state`
* 7. If `cookie.state == SUPPORTED` and `cookie.client_ts` evaluation exceeds
* `COOKIE_CLIENT_TIMEOUT`:
* - clear `cookie.server`
* - randomly generate new `cookie.client`
* - set `cookie.client_ts` to the current timestamp.
* - set `cookie.client_ip` to the current source ip address.
* - do not change the `cookie.state`
* 8. Generate EDNS OPT record (10) for client cookie. The option value will be
* the `cookie.client` concatenated with the `cookie.server`. If there is no
* known server cookie, it will not be appended. Copy `cookie.client` to
* `query.client_cookie` to handle possible client cookie changes by other
* queries before a reply is received (technically this is in the cached
* `ares_dns_record_t` so no need to manually do this). Send request to
* server.
* 9. Evaluate response:
* 1. If invalid EDNS OPT cookie (10) length sent back in response (valid
* length is 16-40), or bad client cookie value (validate first 8 bytes
* against `query.client_cookie` not `cookie.client`), **drop response**
* as if it hadn't been received. This is likely a spoofing attack.
* Wait for valid response up to normal response timeout.
* 2. If a EDNS OPT cookie (10) server cookie is returned:
* - set `cookie.unsupported_ts` to zero and `cookie.state = SUPPORTED`.
* We can confirm this server supports cookies based on the existence
* of this record.
* - If a new EDNS OPT cookie (10) server cookie is in the response, and
* the `client.cookie` matches the `query.client_cookie` still (hasn't
* been rotated by some other parallel query), save it as
* `cookie.server`.
* 3. If dns response `rcode` is `BAD_COOKIE`:
* - Ensure a EDNS OPT cookie (10) is returned, otherwise **drop
* response**, this is completely invalid and likely an spoof of some
* sort.
* - Otherwise
* - Increment `query.cookie_try_count`
* - If `query.cookie_try_count >= COOKIE_RESEND_MAX`, set
* `query.using_tcp` to force the next attempt to use TCP.
* - **Requeue the query**, but do not increment the normal
* `try_count` as a `BAD_COOKIE` reply isn't a normal try failure.
* This should end up going all the way back to step 1 on the next
* attempt.
* 4. If EDNS OPT cookie (10) is **NOT** returned in the response:
* - If `cookie.state == SUPPORTED`
* - if `cookie.unsupported_ts` is zero, set to the current timestamp.
* - Drop the response, wait for a valid response to be returned
* - if `cookie.state == GENERATED`
* - clear all cookie settings
* - set `cookie.state = UNSUPPORTED`
* - set `cookie.unsupported_ts` to the current time
* - Accept response (state should be `UNSUPPORTED` if we're here)
*/
#include "ares_private.h"
/* 1 day */
#define COOKIE_CLIENT_TIMEOUT_MS (86400 * 1000)
/* 5 minutes */
#define COOKIE_UNSUPPORTED_TIMEOUT_MS (300 * 1000)
/* 2 minutes */
#define COOKIE_REGRESSION_TIMEOUT_MS (120 * 1000)
#define COOKIE_RESEND_MAX 3
static const unsigned char *
ares_dns_cookie_fetch(const ares_dns_record_t *dnsrec, size_t *len)
{
const ares_dns_rr_t *rr = ares_dns_get_opt_rr_const(dnsrec);
const unsigned char *val = NULL;
*len = 0;
if (rr == NULL) {
return NULL;
}
if (!ares_dns_rr_get_opt_byid(rr, ARES_RR_OPT_OPTIONS, ARES_OPT_PARAM_COOKIE,
&val, len)) {
return NULL;
}
return val;
}
static ares_bool_t timeval_is_set(const ares_timeval_t *tv)
{
if (tv->sec != 0 && tv->usec != 0) {
return ARES_TRUE;
}
return ARES_FALSE;
}
static ares_bool_t timeval_expired(const ares_timeval_t *tv,
const ares_timeval_t *now,
unsigned long millsecs)
{
ares_int64_t tvdiff_ms;
ares_timeval_t tvdiff;
ares__timeval_diff(&tvdiff, tv, now);
tvdiff_ms = tvdiff.sec * 1000 + tvdiff.usec / 1000;
if (tvdiff_ms >= (ares_int64_t)millsecs) {
return ARES_TRUE;
}
return ARES_FALSE;
}
static void ares_cookie_clear(ares_cookie_t *cookie)
{
memset(cookie, 0, sizeof(*cookie));
cookie->state = ARES_COOKIE_INITIAL;
}
static void ares_cookie_generate(ares_cookie_t *cookie,
struct server_connection *conn,
const ares_timeval_t *now)
{
ares_channel_t *channel = conn->server->channel;
ares__rand_bytes(channel->rand_state, cookie->client, sizeof(cookie->client));
memcpy(&cookie->client_ts, now, sizeof(cookie->client_ts));
memcpy(&cookie->client_ip, &conn->self_ip, sizeof(cookie->client_ip));
}
static void ares_cookie_clear_server(ares_cookie_t *cookie)
{
memset(cookie->server, 0, sizeof(cookie->server));
cookie->server_len = 0;
}
static ares_bool_t ares_addr_equal(const struct ares_addr *addr1,
const struct ares_addr *addr2)
{
if (addr1->family != addr2->family) {
return ARES_FALSE;
}
switch (addr1->family) {
case AF_INET:
if (memcmp(&addr1->addr.addr4, &addr2->addr.addr4,
sizeof(addr1->addr.addr4)) == 0) {
return ARES_TRUE;
}
break;
case AF_INET6:
if (memcmp(&addr1->addr.addr6, &addr2->addr.addr6,
sizeof(addr1->addr.addr6)) == 0) {
return ARES_TRUE;
}
break;
default:
break; /* LCOV_EXCL_LINE */
}
return ARES_FALSE;
}
ares_status_t ares_cookie_apply(ares_dns_record_t *dnsrec,
struct server_connection *conn,
const ares_timeval_t *now)
{
struct server_state *server = conn->server;
ares_cookie_t *cookie = &server->cookie;
ares_dns_rr_t *rr = ares_dns_get_opt_rr(dnsrec);
unsigned char c[40];
size_t c_len;
/* If there is no OPT record, then EDNS isn't supported, and therefore
* cookies can't be supported */
if (rr == NULL) {
return ARES_SUCCESS;
}
/* No cookies on TCP, make sure we remove one if one is present */
if (conn->is_tcp) {
ares_dns_rr_del_opt_byid(rr, ARES_RR_OPT_OPTIONS, ARES_OPT_PARAM_COOKIE);
return ARES_SUCCESS;
}
/* Look for regression */
if (cookie->state == ARES_COOKIE_SUPPORTED &&
timeval_is_set(&cookie->unsupported_ts) &&
timeval_expired(&cookie->unsupported_ts, now,
COOKIE_REGRESSION_TIMEOUT_MS)) {
ares_cookie_clear(cookie);
}
/* Handle unsupported state */
if (cookie->state == ARES_COOKIE_UNSUPPORTED) {
/* If timer hasn't expired, just delete any possible cookie and return */
if (!timeval_expired(&cookie->unsupported_ts, now,
COOKIE_REGRESSION_TIMEOUT_MS)) {
ares_dns_rr_del_opt_byid(rr, ARES_RR_OPT_OPTIONS, ARES_OPT_PARAM_COOKIE);
return ARES_SUCCESS;
}
/* We want to try to "learn" again */
ares_cookie_clear(cookie);
}
/* Generate a new cookie */
if (cookie->state == ARES_COOKIE_INITIAL) {
ares_cookie_generate(cookie, conn, now);
cookie->state = ARES_COOKIE_GENERATED;
}
/* Regenerate the cookie and clear the server cookie if the client ip has
* changed */
if ((cookie->state == ARES_COOKIE_GENERATED ||
cookie->state == ARES_COOKIE_SUPPORTED) &&
!ares_addr_equal(&conn->self_ip, &cookie->client_ip)) {
ares_cookie_clear_server(cookie);
ares_cookie_generate(cookie, conn, now);
}
/* If the client cookie has reached its maximum time, refresh it */
if (cookie->state == ARES_COOKIE_SUPPORTED &&
timeval_expired(&cookie->client_ts, now, COOKIE_CLIENT_TIMEOUT_MS)) {
ares_cookie_clear_server(cookie);
ares_cookie_generate(cookie, conn, now);
}
/* Generate the full cookie which is the client cookie concatenated with the
* server cookie (if there is one) and apply it. */
memcpy(c, cookie->client, sizeof(cookie->client));
if (cookie->server_len) {
memcpy(c + sizeof(cookie->client), cookie->server, cookie->server_len);
}
c_len = sizeof(cookie->client) + cookie->server_len;
return ares_dns_rr_set_opt(rr, ARES_RR_OPT_OPTIONS, ARES_OPT_PARAM_COOKIE, c,
c_len);
}
ares_status_t ares_cookie_validate(struct query *query,
const ares_dns_record_t *dnsresp,
struct server_connection *conn,
const ares_timeval_t *now)
{
struct server_state *server = conn->server;
ares_cookie_t *cookie = &server->cookie;
const ares_dns_record_t *dnsreq = query->query;
const unsigned char *resp_cookie;
size_t resp_cookie_len;
const unsigned char *req_cookie;
size_t req_cookie_len;
resp_cookie = ares_dns_cookie_fetch(dnsresp, &resp_cookie_len);
/* Invalid cookie length, drop */
if (resp_cookie && (resp_cookie_len < 8 || resp_cookie_len > 40)) {
return ARES_EBADRESP;
}
req_cookie = ares_dns_cookie_fetch(dnsreq, &req_cookie_len);
/* Didn't request cookies, so we can stop evaluating */
if (req_cookie == NULL) {
return ARES_SUCCESS;
}
/* If 8-byte prefix for returned cookie doesn't match the requested cookie,
* drop for spoofing */
if (resp_cookie && memcmp(req_cookie, resp_cookie, 8) != 0) {
return ARES_EBADRESP;
}
if (resp_cookie && resp_cookie_len > 8) {
/* Make sure we record that we successfully received a cookie response */
cookie->state = ARES_COOKIE_SUPPORTED;
memset(&cookie->unsupported_ts, 0, sizeof(cookie->unsupported_ts));
/* If client cookie hasn't been rotated, save the returned server cookie */
if (memcmp(cookie->client, req_cookie, sizeof(cookie->client)) == 0) {
memcpy(cookie->server, resp_cookie + 8, resp_cookie_len - 8);
}
}
if (ares_dns_record_get_rcode(dnsresp) == ARES_RCODE_BADCOOKIE) {
/* Illegal to return BADCOOKIE but no cookie, drop */
if (resp_cookie == NULL) {
return ARES_EBADRESP;
}
/* If we have too many attempts to send a cookie, we need to requeue as
* tcp */
query->cookie_try_count++;
if (query->cookie_try_count >= COOKIE_RESEND_MAX) {
query->using_tcp = ARES_TRUE;
}
/* Resend the request, hopefully it will work the next time as we should
* have recorded a server cookie */
ares__requeue_query(query, now, ARES_SUCCESS,
ARES_FALSE /* Don't increment try count */);
/* Parent needs to drop this response */
return ARES_EBADRESP;
}
/* We've got a response with a server cookie, and we've done all the
* evaluation we can, return success */
if (resp_cookie_len > 8) {
return ARES_SUCCESS;
}
if (cookie->state == ARES_COOKIE_SUPPORTED) {
/* If we're not currently tracking an error time yet, start */
if (!timeval_is_set(&cookie->unsupported_ts)) {
memcpy(&cookie->unsupported_ts, now, sizeof(cookie->unsupported_ts));
}
/* Drop it since we expected a cookie */
return ARES_EBADRESP;
}
if (cookie->state == ARES_COOKIE_GENERATED) {
ares_cookie_clear(cookie);
cookie->state = ARES_COOKIE_UNSUPPORTED;
memcpy(&cookie->unsupported_ts, now, sizeof(cookie->unsupported_ts));
}
/* Cookie state should be UNSUPPORTED if we're here */
return ARES_SUCCESS;
}

@ -38,22 +38,23 @@ ares_bool_t ares_dns_class_isvalid(ares_dns_class_t qclass,
ares_dns_rec_type_t type,
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,
ares_dns_rr_key_t key, unsigned char *val,
size_t len);
ares_status_t ares_dns_rr_set_abin_own(ares_dns_rr_t *dns_rr,
ares_dns_rr_key_t key,
ares__dns_multistring_t *strs);
ares_status_t ares_dns_rr_set_opt_own(ares_dns_rr_t *dns_rr,
ares_dns_rr_key_t key, unsigned short opt,
unsigned char *val, size_t val_len);
ares_status_t ares_dns_record_rr_prealloc(ares_dns_record_t *dnsrec,
ares_dns_section_t sect, size_t cnt);
ares_bool_t ares_dns_has_opt_rr(const ares_dns_record_t *rec);
void ares_dns_record_write_ttl_decrement(ares_dns_record_t *dnsrec,
unsigned int ttl_decrement);
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,
ares_dns_rr_key_t key, unsigned char *val,
size_t len);
ares_status_t ares_dns_rr_set_abin_own(ares_dns_rr_t *dns_rr,
ares_dns_rr_key_t key,
ares__dns_multistring_t *strs);
ares_status_t ares_dns_rr_set_opt_own(ares_dns_rr_t *dns_rr,
ares_dns_rr_key_t key, unsigned short opt,
unsigned char *val, size_t val_len);
ares_status_t ares_dns_record_rr_prealloc(ares_dns_record_t *dnsrec,
ares_dns_section_t sect, size_t cnt);
ares_dns_rr_t *ares_dns_get_opt_rr(ares_dns_record_t *rec);
const ares_dns_rr_t *ares_dns_get_opt_rr_const(const ares_dns_record_t *rec);
void ares_dns_record_write_ttl_decrement(ares_dns_record_t *dnsrec,
unsigned int ttl_decrement);
/*! Create a DNS record object for a query. The arguments are the same as
* those for ares_create_query().

@ -1460,6 +1460,51 @@ ares_status_t ares_dns_rr_set_opt(ares_dns_rr_t *dns_rr, ares_dns_rr_key_t key,
return status;
}
ares_status_t ares_dns_rr_del_opt_byid(ares_dns_rr_t *dns_rr,
ares_dns_rr_key_t key,
unsigned short opt)
{
ares__dns_options_t **options;
size_t idx;
size_t cnt_after;
if (ares_dns_rr_key_datatype(key) != ARES_DATATYPE_OPT) {
return ARES_EFORMERR;
}
options = ares_dns_rr_data_ptr(dns_rr, key, NULL);
if (options == NULL) {
return ARES_EFORMERR;
}
/* No options */
if (*options == NULL) {
return ARES_SUCCESS;
}
for (idx = 0; idx < (*options)->cnt; idx++) {
if ((*options)->optval[idx].opt == opt) {
break;
}
}
/* No matching option */
if (idx == (*options)->cnt) {
return ARES_ENOTFOUND;
}
ares_free((*options)->optval[idx].val);
cnt_after = (*options)->cnt - idx - 1;
if (cnt_after) {
memmove(&(*options)->optval[idx], &(*options)->optval[idx + 1],
sizeof(*(*options)->optval) * cnt_after);
}
(*options)->cnt--;
return ARES_SUCCESS;
}
char *ares_dns_addr_to_ptr(const struct ares_addr *addr)
{
ares__buf_t *buf = NULL;
@ -1532,8 +1577,20 @@ fail:
return NULL;
}
/* search for an OPT RR in the response */
ares_bool_t ares_dns_has_opt_rr(const ares_dns_record_t *rec)
ares_dns_rr_t *ares_dns_get_opt_rr(ares_dns_record_t *rec)
{
size_t i;
for (i = 0; i < ares_dns_record_rr_cnt(rec, ARES_SECTION_ADDITIONAL); i++) {
ares_dns_rr_t *rr = ares_dns_record_rr_get(rec, ARES_SECTION_ADDITIONAL, i);
if (ares_dns_rr_get_type(rr) == ARES_REC_TYPE_OPT) {
return rr;
}
}
return NULL;
}
const ares_dns_rr_t *ares_dns_get_opt_rr_const(const ares_dns_record_t *rec)
{
size_t i;
for (i = 0; i < ares_dns_record_rr_cnt(rec, ARES_SECTION_ADDITIONAL); i++) {
@ -1541,10 +1598,10 @@ ares_bool_t ares_dns_has_opt_rr(const ares_dns_record_t *rec)
ares_dns_record_rr_get_const(rec, ARES_SECTION_ADDITIONAL, i);
if (ares_dns_rr_get_type(rr) == ARES_REC_TYPE_OPT) {
return ARES_TRUE;
return rr;
}
}
return ARES_FALSE;
return NULL;
}
/* Construct a DNS record for a name with given class and type. Used internally

@ -91,7 +91,7 @@ static ares_status_t ares_dns_write_header(const ares_dns_record_t *dnsrec,
}
/* RCODE */
if (dnsrec->rcode > 15 && !ares_dns_has_opt_rr(dnsrec)) {
if (dnsrec->rcode > 15 && ares_dns_get_opt_rr_const(dnsrec) == NULL) {
/* Must have OPT RR in order to write extended error codes */
rcode = ARES_RCODE_SERVFAIL;
} else {

@ -157,6 +157,7 @@ struct server_state;
struct server_connection {
struct server_state *server;
ares_socket_t fd;
struct ares_addr self_ip;
ares_bool_t is_tcp;
/* total number of queries run on this connection since it was established */
size_t total_queries;
@ -204,6 +205,36 @@ typedef struct {
ares_uint64_t prev_total_count; /*!< Previous period bucket query count */
} ares_server_metrics_t;
typedef enum {
ARES_COOKIE_INITIAL = 0,
ARES_COOKIE_GENERATED = 1,
ARES_COOKIE_SUPPORTED = 2,
ARES_COOKIE_UNSUPPORTED = 3
} ares_cookie_state_t;
/*! Structure holding tracking data for RFC 7873/9018 DNS cookies.
* Implementation plan for this feature is here:
* https://github.com/c-ares/c-ares/issues/620
*/
typedef struct {
/*! starts at INITIAL, transitions as needed. */
ares_cookie_state_t state;
/*! randomly-generate client cookie */
unsigned char client[8];
/*! timestamp client cookie was generated, used for rotation purposes */
ares_timeval_t client_ts;
/*! IP address last used for client to connect to server. If this changes
* The client cookie gets invalidated */
struct ares_addr client_ip;
/*! Server Cookie last received, 8-32 bytes in length */
unsigned char server[32];
/*! Length of server cookie on file. */
size_t server_len;
/*! Timestamp of last attempt to use cookies, but it was determined that the
* server didn't support them */
ares_timeval_t unsupported_ts;
} ares_cookie_t;
struct server_state {
/* Configuration */
size_t idx; /* index for server in system configuration */
@ -232,6 +263,9 @@ struct server_state {
/*! Buckets for collecting metrics about the server */
ares_server_metrics_t metrics[ARES_METRIC_COUNT];
/*! RFC 7873/9018 DNS Cookies */
ares_cookie_t cookie;
/* Link back to owning channel */
ares_channel_t *channel;
};
@ -263,11 +297,12 @@ struct query {
/* Query status */
size_t try_count; /* Number of times we tried this query already. */
size_t cookie_try_count; /* Attempt count for cookie resends */
ares_bool_t using_tcp;
ares_status_t error_status;
size_t timeouts; /* number of timeouts we saw for this request */
ares_bool_t no_retries; /* do not perform any additional retries, this is set
* when a query is to be canceled */
size_t timeouts; /* number of timeouts we saw for this request */
ares_bool_t no_retries; /* do not perform any additional retries, this is
* set when a query is to be canceled */
};
struct apattern {
@ -411,7 +446,8 @@ ares_bool_t ares__timedout(const ares_timeval_t *now,
ares_status_t ares__send_query(struct query *query, const ares_timeval_t *now);
ares_status_t ares__requeue_query(struct query *query,
const ares_timeval_t *now,
ares_status_t status);
ares_status_t status,
ares_bool_t inc_try_count);
/*! Retrieve a list of names to use for searching. The first successful
* query in the list wins. This function also uses the HOSTSALIASES file
@ -553,6 +589,9 @@ ares_status_t ares__addrinfo_localhost(const char *name, unsigned short port,
ares_status_t ares__open_connection(ares_channel_t *channel,
struct server_state *server,
ares_bool_t is_tcp);
ares_bool_t ares_sockaddr_to_ares_addr(struct ares_addr *ares_addr,
unsigned short *port,
const struct sockaddr *sockaddr);
ares_socket_t ares__open_socket(ares_channel_t *channel, int af, int type,
int protocol);
ares_ssize_t ares__socket_write(ares_channel_t *channel, ares_socket_t s,
@ -715,6 +754,14 @@ void ares_metrics_record(const struct query *query, struct server_state *server,
size_t ares_metrics_server_timeout(const struct server_state *server,
const ares_timeval_t *now);
ares_status_t ares_cookie_apply(ares_dns_record_t *dnsrec,
struct server_connection *conn,
const ares_timeval_t *now);
ares_status_t ares_cookie_validate(struct query *query,
const ares_dns_record_t *dnsresp,
struct server_connection *conn,
const ares_timeval_t *now);
ares_status_t ares__channel_threading_init(ares_channel_t *channel);
void ares__channel_threading_destroy(ares_channel_t *channel);
void ares__channel_lock(const ares_channel_t *channel);

@ -623,7 +623,7 @@ static void process_timeouts(ares_channel_t *channel, const ares_timeval_t *now)
conn = query->conn;
server_increment_failures(conn->server, query->using_tcp);
ares__requeue_query(query, now, ARES_ETIMEOUT);
ares__requeue_query(query, now, ARES_ETIMEOUT, ARES_TRUE);
}
}
@ -697,6 +697,14 @@ static ares_status_t process_answer(ares_channel_t *channel,
goto cleanup;
}
/* Validate DNS cookie in response. This function may need to requeue the
* query. */
if (ares_cookie_validate(query, rdnsrec, conn, now) != ARES_SUCCESS) {
/* Drop response and return */
status = ARES_SUCCESS;
goto cleanup;
}
/* At this point we know we've received an answer for this query, so we should
* remove it from the connection's queue so we can possibly invalidate the
* connection. Delay cleaning up the connection though as we may enqueue
@ -708,7 +716,8 @@ static ares_status_t process_answer(ares_channel_t *channel,
* protocol extension is not understood by the responder. We must retry the
* query without EDNS enabled. */
if (ares_dns_record_get_rcode(rdnsrec) == ARES_RCODE_FORMERR &&
ares_dns_has_opt_rr(query->query) && !ares_dns_has_opt_rr(rdnsrec)) {
ares_dns_get_opt_rr_const(query->query) != NULL &&
ares_dns_get_opt_rr_const(rdnsrec) == NULL) {
status = rewrite_without_edns(query);
if (status != ARES_SUCCESS) {
end_query(channel, server, query, status, NULL);
@ -754,7 +763,7 @@ static ares_status_t process_answer(ares_channel_t *channel,
}
server_increment_failures(server, query->using_tcp);
ares__requeue_query(query, now, status);
ares__requeue_query(query, now, status, ARES_TRUE);
/* Should any of these cause a connection termination?
* Maybe SERVER_FAILURE? */
@ -801,7 +810,8 @@ static void handle_conn_error(struct server_connection *conn,
ares_status_t ares__requeue_query(struct query *query,
const ares_timeval_t *now,
ares_status_t status)
ares_status_t status,
ares_bool_t inc_try_count)
{
ares_channel_t *channel = query->channel;
size_t max_tries = ares__slist_len(channel->servers) * channel->tries;
@ -812,7 +822,10 @@ ares_status_t ares__requeue_query(struct query *query,
query->error_status = status;
}
query->try_count++;
if (inc_try_count) {
query->try_count++;
}
if (query->try_count < max_tries && !query->no_retries) {
return ares__send_query(query, now);
}
@ -923,44 +936,57 @@ static struct server_state *ares__failover_server(ares_channel_t *channel)
return first_server;
}
static ares_status_t ares__append_tcpbuf(struct server_state *server,
const struct query *query)
static ares_status_t ares__append_tcpbuf(struct server_connection *conn,
const struct query *query,
const ares_timeval_t *now)
{
ares_status_t status;
unsigned char *qbuf = NULL;
size_t qbuf_len = 0;
status = ares_cookie_apply(query->query, conn, now);
if (status != ARES_SUCCESS) {
goto done;
}
status = ares_dns_write(query->query, &qbuf, &qbuf_len);
if (status != ARES_SUCCESS) {
goto done;
}
status = ares__buf_append_be16(server->tcp_send, (unsigned short)qbuf_len);
status =
ares__buf_append_be16(conn->server->tcp_send, (unsigned short)qbuf_len);
if (status != ARES_SUCCESS) {
goto done; /* LCOV_EXCL_LINE: OutOfMemory */
}
status = ares__buf_append(server->tcp_send, qbuf, qbuf_len);
status = ares__buf_append(conn->server->tcp_send, qbuf, qbuf_len);
done:
ares_free(qbuf);
return status;
}
static ares_status_t ares__write_udpbuf(ares_channel_t *channel,
ares_socket_t fd,
const struct query *query)
static ares_status_t ares__write_udpbuf(struct server_connection *conn,
const struct query *query,
const ares_timeval_t *now)
{
ares_status_t status;
unsigned char *qbuf = NULL;
size_t qbuf_len = 0;
status = ares_cookie_apply(query->query, conn, now);
if (status != ARES_SUCCESS) {
goto done;
}
status = ares_dns_write(query->query, &qbuf, &qbuf_len);
if (status != ARES_SUCCESS) {
goto done;
}
if (ares__socket_write(channel, fd, qbuf, qbuf_len) == -1) {
if (ares__socket_write(conn->server->channel, conn->fd, qbuf, qbuf_len) ==
-1) {
if (try_again(SOCKERRNO)) {
status = ARES_ESERVFAIL;
} else {
@ -1071,7 +1097,7 @@ ares_status_t ares__send_query(struct query *query, const ares_timeval_t *now)
case ARES_ECONNREFUSED:
case ARES_EBADFAMILY:
server_increment_failures(server, query->using_tcp);
return ares__requeue_query(query, now, status);
return ares__requeue_query(query, now, status, ARES_TRUE);
/* Anything else is not retryable, likely ENOMEM */
default:
@ -1084,7 +1110,7 @@ ares_status_t ares__send_query(struct query *query, const ares_timeval_t *now)
prior_len = ares__buf_len(server->tcp_send);
status = ares__append_tcpbuf(server, query);
status = ares__append_tcpbuf(conn, query, now);
if (status != ARES_SUCCESS) {
end_query(channel, server, query, status, NULL);
@ -1129,7 +1155,7 @@ ares_status_t ares__send_query(struct query *query, const ares_timeval_t *now)
case ARES_ECONNREFUSED:
case ARES_EBADFAMILY:
server_increment_failures(server, query->using_tcp);
return ares__requeue_query(query, now, status);
return ares__requeue_query(query, now, status, ARES_TRUE);
/* Anything else is not retryable, likely ENOMEM */
default:
@ -1141,7 +1167,7 @@ ares_status_t ares__send_query(struct query *query, const ares_timeval_t *now)
conn = ares__llist_node_val(node);
status = ares__write_udpbuf(channel, conn->fd, query);
status = ares__write_udpbuf(conn, query, now);
if (status != ARES_SUCCESS) {
if (status == ARES_ENOMEM) {
/* Not retryable */
@ -1154,7 +1180,7 @@ ares_status_t ares__send_query(struct query *query, const ares_timeval_t *now)
/* This query wasn't yet bound to the connection, need to manually
* requeue it and return an appropriate error */
status = ares__requeue_query(query, now, status);
status = ares__requeue_query(query, now, status, ARES_TRUE);
if (status == ARES_ETIMEOUT) {
status = ARES_ECONNREFUSED;
}
@ -1164,7 +1190,7 @@ ares_status_t ares__send_query(struct query *query, const ares_timeval_t *now)
/* FIXME: Handle EAGAIN here since it likely can happen. Right now we
* just requeue to a different server/connection. */
server_increment_failures(server, query->using_tcp);
status = ares__requeue_query(query, now, status);
status = ares__requeue_query(query, now, status, ARES_TRUE);
/* Only safe to kill connection if it was new, otherwise it should be
* cleaned up by another process later */

@ -274,25 +274,7 @@ static ares_status_t read_resolver(const dns_resolver_t *resolver,
/* UBSAN alignment workaround to fetch memory address */
memcpy(&sockaddr, resolver->nameserver + i, sizeof(sockaddr));
if (sockaddr->sa_family == AF_INET) {
/* NOTE: memcpy sockaddr_in due to alignment issues found by UBSAN due to
* dnsinfo packing */
struct sockaddr_in addr_in;
memcpy(&addr_in, sockaddr, sizeof(addr_in));
addr.family = AF_INET;
memcpy(&addr.addr.addr4, &(addr_in.sin_addr), sizeof(addr.addr.addr4));
addrport = ntohs(addr_in.sin_port);
} else if (sockaddr->sa_family == AF_INET6) {
/* NOTE: memcpy sockaddr_in6 due to alignment issues found by UBSAN due to
* dnsinfo packing */
struct sockaddr_in6 addr_in6;
memcpy(&addr_in6, sockaddr, sizeof(addr_in6));
addr.family = AF_INET6;
memcpy(&addr.addr.addr6, &(addr_in6.sin6_addr), sizeof(addr.addr.addr6));
addrport = ntohs(addr_in6.sin6_port);
} else {
if (!ares_sockaddr_to_ares_addr(&addr, &addrport, sockaddr)) {
continue;
}

@ -143,13 +143,13 @@ ares_status_t ares_event_update(ares_event_t **event, ares_event_thread_t *e,
ev = ares_malloc_zero(sizeof(*ev));
if (ev == NULL) {
status = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
goto done; /* LCOV_EXCL_LINE: OutOfMemory */
goto done; /* LCOV_EXCL_LINE: OutOfMemory */
}
if (ares__llist_insert_last(e->ev_updates, ev) == NULL) {
ares_free(ev); /* LCOV_EXCL_LINE: OutOfMemory */
ares_free(ev); /* LCOV_EXCL_LINE: OutOfMemory */
status = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
goto done; /* LCOV_EXCL_LINE: OutOfMemory */
goto done; /* LCOV_EXCL_LINE: OutOfMemory */
}
}

@ -754,18 +754,31 @@ static void print_section(ares_dns_record_t *dnsrec, ares_dns_section_t section)
static void print_opt_psuedosection(ares_dns_record_t *dnsrec)
{
const ares_dns_rr_t *rr = has_opt(dnsrec, ARES_SECTION_ADDITIONAL);
const ares_dns_rr_t *rr = has_opt(dnsrec, ARES_SECTION_ADDITIONAL);
const unsigned char *cookie = NULL;
size_t cookie_len = 0;
if (rr == NULL) {
return;
}
if (!ares_dns_rr_get_opt_byid(rr, ARES_RR_OPT_OPTIONS, ARES_OPT_PARAM_COOKIE,
&cookie, &cookie_len)) {
cookie = NULL;
}
printf(";; OPT PSEUDOSECTION:\n");
printf("; EDNS: version: %u, flags: %u; udp: %u\t",
printf("; EDNS: version: %u, flags: %u; udp: %u\n",
(unsigned int)ares_dns_rr_get_u8(rr, ARES_RR_OPT_VERSION),
(unsigned int)ares_dns_rr_get_u16(rr, ARES_RR_OPT_FLAGS),
(unsigned int)ares_dns_rr_get_u16(rr, ARES_RR_OPT_UDP_SIZE));
printf("\n");
if (cookie) {
printf("; COOKIE: ");
print_opt_bin(cookie, cookie_len);
printf(" (good)\n");
}
}
static void callback(void *arg, int status, int timeouts, unsigned char *abuf,

@ -519,7 +519,7 @@ TEST_F(LibraryTest, CreateEDNSQuery) {
std::string actual = PacketToString(data);
DNSPacket pkt;
pkt.set_qid(0x1234).add_question(new DNSQuestion("example.com", T_A))
.add_additional(new DNSOptRR(0, 1280));
.add_additional(new DNSOptRR(0, 0, 0, 1280, { }, { } /* No server cookie */));
std::string expected = PacketToString(pkt.data());
EXPECT_EQ(expected, actual);
}

@ -1005,7 +1005,9 @@ TEST_P(MockChannelTest, SearchHighNdots) {
// Test that performing an EDNS search with an OPT RR options value works. The
// options value should be included on the requests to the mock server.
TEST_P(MockEDNSChannelTest, SearchOptVal) {
// We are going to do this only via TCP since this won't include the dynamically
// generated DNS cookie that would otherwise mess with this result.
TEST_P(MockTCPChannelTest, SearchOptVal) {
/* Define the OPT RR options code and value to use. */
unsigned short opt_opt = 3;
unsigned char opt_val[] = { 'c', '-', 'a', 'r', 'e', 's' };
@ -1511,6 +1513,264 @@ TEST_P(MockChannelTest, GetHostByAddrDestroy) {
EXPECT_EQ(0, result.timeouts_);
}
static const unsigned char *
fetch_server_cookie(const ares_dns_record_t *dnsrec, size_t *len)
{
const ares_dns_rr_t *rr = fetch_rr_opt(dnsrec);
const unsigned char *val = NULL;
*len = 0;
if (rr == NULL) {
return NULL;
}
if (!ares_dns_rr_get_opt_byid(rr, ARES_RR_OPT_OPTIONS, ARES_OPT_PARAM_COOKIE,
&val, len)) {
return NULL;
}
if (*len <= 8) {
*len = 0;
return NULL;
}
*len -= 8;
val += 8;
return val;
}
static const unsigned char *
fetch_client_cookie(const ares_dns_record_t *dnsrec, size_t *len)
{
const ares_dns_rr_t *rr = fetch_rr_opt(dnsrec);
const unsigned char *val = NULL;
*len = 0;
if (rr == NULL) {
return NULL;
}
if (!ares_dns_rr_get_opt_byid(rr, ARES_RR_OPT_OPTIONS, ARES_OPT_PARAM_COOKIE,
&val, len)) {
return NULL;
}
if (*len < 8) {
*len = 0;
return NULL;
}
*len = 8;
return val;
}
TEST_P(MockUDPChannelTest, DNSCookieSingle) {
DNSPacket reply;
std::vector<byte> server_cookie = { 1, 2, 3, 4, 5, 6, 7, 8 };
reply.set_response().set_aa()
.add_question(new DNSQuestion("www.google.com", T_A))
.add_answer(new DNSARR("www.google.com", 0x0100, {0x01, 0x02, 0x03, 0x04}))
.add_additional(new DNSOptRR(0, 0, 0, 1280, { }, server_cookie));
EXPECT_CALL(server_, OnRequest("www.google.com", T_A))
.WillOnce(SetReply(&server_, &reply));
QueryResult result;
ares_query_dnsrec(channel_, "www.google.com", ARES_CLASS_IN, ARES_REC_TYPE_A, QueryCallback, &result, NULL);
Process();
EXPECT_TRUE(result.done_);
EXPECT_EQ(0, result.timeouts_);
size_t len;
const unsigned char *returned_cookie = fetch_server_cookie(result.dnsrec_.dnsrec_, &len);
EXPECT_EQ(len, server_cookie.size());
EXPECT_TRUE(memcmp(server_cookie.data(), returned_cookie, len) == 0);
}
TEST_P(MockUDPChannelTest, DNSCookieMissingAfterGood) {
DNSPacket reply;
std::vector<byte> server_cookie = { 1, 2, 3, 4, 5, 6, 7, 8 };
reply.set_response().set_aa()
.add_question(new DNSQuestion("www.google.com", T_A))
.add_answer(new DNSARR("www.google.com", 0x0100, {0x01, 0x02, 0x03, 0x04}))
.add_additional(new DNSOptRR(0, 0, 0, 1280, { }, server_cookie));
DNSPacket reply_nocookie;
reply_nocookie.set_response().set_aa()
.add_question(new DNSQuestion("www.google.com", T_A))
.add_answer(new DNSARR("www.google.com", 0x0100, {0x01, 0x02, 0x03, 0x04}))
.add_additional(new DNSOptRR(0, 0, 0, 1280, { }, { }));
EXPECT_CALL(server_, OnRequest("www.google.com", T_A))
.WillOnce(SetReply(&server_, &reply))
.WillOnce(SetReply(&server_, &reply_nocookie))
.WillOnce(SetReply(&server_, &reply));
/* This test will establish the server supports cookies, then the next reply
* will be missing the server cookie and therefore be rejected and timeout, then
* an internal retry will occur and the cookie will be present again. */
QueryResult result1;
ares_query_dnsrec(channel_, "www.google.com", ARES_CLASS_IN, ARES_REC_TYPE_A, QueryCallback, &result1, NULL);
Process();
EXPECT_TRUE(result1.done_);
EXPECT_EQ(0, result1.timeouts_);
QueryResult result2;
ares_query_dnsrec(channel_, "www.google.com", ARES_CLASS_IN, ARES_REC_TYPE_A, QueryCallback, &result2, NULL);
Process();
EXPECT_TRUE(result2.done_);
EXPECT_EQ(1, result2.timeouts_);
/* Client cookie should NOT have rotated */
size_t len1;
const unsigned char *client_cookie_1 = fetch_client_cookie(result1.dnsrec_.dnsrec_, &len1);
size_t len2;
const unsigned char *client_cookie_2 = fetch_client_cookie(result2.dnsrec_.dnsrec_, &len2);
EXPECT_EQ(len1, 8);
EXPECT_EQ(len1, len2);
EXPECT_TRUE(memcmp(client_cookie_1, client_cookie_2, len1) == 0);
}
TEST_P(MockUDPChannelTest, DNSCookieBadLen) {
std::vector<byte> server_cookie = { 1, 2, 3, 4, 5, 6, 7, 8 };
std::vector<byte> server_cookie_bad = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0 };
DNSPacket reply;
reply.set_response().set_aa()
.add_question(new DNSQuestion("www.google.com", T_A))
.add_answer(new DNSARR("www.google.com", 0x0100, {0x01, 0x02, 0x03, 0x04}))
.add_additional(new DNSOptRR(0, 0, 0, 1280, { }, server_cookie));
DNSPacket reply_badcookielen;
reply_badcookielen.set_response().set_aa()
.add_question(new DNSQuestion("www.google.com", T_A))
.add_answer(new DNSARR("www.google.com", 0x0100, {0x01, 0x02, 0x03, 0x04}))
.add_additional(new DNSOptRR(0, 0, 0, 1280, { }, server_cookie_bad ));
EXPECT_CALL(server_, OnRequest("www.google.com", T_A))
.WillOnce(SetReply(&server_, &reply_badcookielen))
.WillOnce(SetReply(&server_, &reply));
/* This test will send back a malformed cookie len, then when it times out and retry occurs will send back a valid cookie. */
QueryResult result1;
ares_query_dnsrec(channel_, "www.google.com", ARES_CLASS_IN, ARES_REC_TYPE_A, QueryCallback, &result1, NULL);
Process();
EXPECT_TRUE(result1.done_);
EXPECT_EQ(1, result1.timeouts_);
}
TEST_P(MockUDPChannelTest, DNSCookieServerRotate) {
std::vector<byte> server_cookie = { 1, 2, 3, 4, 5, 6, 7, 8 };
std::vector<byte> server_cookie_rotate = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF };
DNSPacket reply_cookie1;
reply_cookie1.set_response().set_aa()
.add_question(new DNSQuestion("www.google.com", T_A))
.add_answer(new DNSARR("www.google.com", 0x0100, {0x01, 0x02, 0x03, 0x04}))
.add_additional(new DNSOptRR(0, 0, 0, 1280, {}, server_cookie));
DNSPacket reply_cookie2_badcookie;
reply_cookie2_badcookie.set_response().set_aa().set_rcode(ARES_RCODE_BADCOOKIE & 0xF)
.add_question(new DNSQuestion("www.google.com", T_A))
.add_answer(new DNSARR("www.google.com", 0x0100, {0x01, 0x02, 0x03, 0x04}))
.add_additional(new DNSOptRR((ARES_RCODE_BADCOOKIE >> 4) & 0xFF, 0, 0, 1280, { }, server_cookie_rotate));
DNSPacket reply_cookie2;
reply_cookie2.set_response().set_aa()
.add_question(new DNSQuestion("www.google.com", T_A))
.add_answer(new DNSARR("www.google.com", 0x0100, {0x01, 0x02, 0x03, 0x04}))
.add_additional(new DNSOptRR(0, 0, 0, 1280, { }, server_cookie_rotate));
EXPECT_CALL(server_, OnRequest("www.google.com", T_A))
.WillOnce(SetReply(&server_, &reply_cookie1))
.WillOnce(SetReply(&server_, &reply_cookie2_badcookie))
.WillOnce(SetReply(&server_, &reply_cookie2));
/* This test will establish the server supports cookies, then the next reply
* the server returns BADCOOKIE indicating the cookie has rotated and
* returns a new cookie. Then the query will be automatically retried with
* the newly returned cookie. No timeouts should be indicated, and the
* client cookie should not rotate. */
QueryResult result1;
ares_query_dnsrec(channel_, "www.google.com", ARES_CLASS_IN, ARES_REC_TYPE_A, QueryCallback, &result1, NULL);
Process();
EXPECT_TRUE(result1.done_);
EXPECT_EQ(0, result1.timeouts_);
QueryResult result2;
ares_query_dnsrec(channel_, "www.google.com", ARES_CLASS_IN, ARES_REC_TYPE_A, QueryCallback, &result2, NULL);
Process();
EXPECT_TRUE(result2.done_);
EXPECT_EQ(0, result2.timeouts_);
/* Client cookie should NOT have rotated */
size_t len1;
const unsigned char *client_cookie_1 = fetch_client_cookie(result1.dnsrec_.dnsrec_, &len1);
size_t len2;
const unsigned char *client_cookie_2 = fetch_client_cookie(result2.dnsrec_.dnsrec_, &len2);
EXPECT_EQ(len1, 8);
EXPECT_EQ(len1, len2);
EXPECT_TRUE(memcmp(client_cookie_1, client_cookie_2, len1) == 0);
}
TEST_P(MockUDPChannelTest, DNSCookieSpoof) {
std::vector<byte> client_cookie = { 1, 2, 3, 4, 5, 6, 7, 8 };
std::vector<byte> server_cookie = { 1, 2, 3, 4, 5, 6, 7, 8 };
DNSPacket reply_spoof;
reply_spoof.set_response().set_aa()
.add_question(new DNSQuestion("www.google.com", T_A))
.add_answer(new DNSARR("www.google.com", 0x0100, {0x01, 0x02, 0x03, 0x04}))
.add_additional(new DNSOptRR(0, 0, 0, 1280, client_cookie, server_cookie));
DNSPacket reply;
reply.set_response().set_aa()
.add_question(new DNSQuestion("www.google.com", T_A))
.add_answer(new DNSARR("www.google.com", 0x0100, {0x01, 0x02, 0x03, 0x04}))
.add_additional(new DNSOptRR(0, 0, 0, 1280, { }, server_cookie));
EXPECT_CALL(server_, OnRequest("www.google.com", T_A))
.WillOnce(SetReply(&server_, &reply_spoof))
.WillOnce(SetReply(&server_, &reply));
/* This test will return a reply that doesn't have the same client cookie as
* was sent, this should result in a drop of the packet alltogether, then
* the library will retry and a proper result will be sent. */
QueryResult result;
ares_query_dnsrec(channel_, "www.google.com", ARES_CLASS_IN, ARES_REC_TYPE_A, QueryCallback, &result, NULL);
Process();
EXPECT_TRUE(result.done_);
EXPECT_EQ(1, result.timeouts_);
}
TEST_P(MockUDPChannelTest, DNSCookieTCPUpgrade) {
std::vector<byte> server_cookie = { 1, 2, 3, 4, 5, 6, 7, 8 };
DNSPacket reply_badcookie;
reply_badcookie.set_response().set_aa().set_rcode(ARES_RCODE_BADCOOKIE & 0xF)
.add_question(new DNSQuestion("www.google.com", T_A))
.add_answer(new DNSARR("www.google.com", 0x0100, {0x01, 0x02, 0x03, 0x04}))
.add_additional(new DNSOptRR((ARES_RCODE_BADCOOKIE >> 4) & 0xFF, 0, 0, 1280, { }, server_cookie));
DNSPacket reply;
reply.set_response().set_aa()
.add_question(new DNSQuestion("www.google.com", T_A))
.add_answer(new DNSARR("www.google.com", 0x0100, {0x01, 0x02, 0x03, 0x04}))
.add_additional(new DNSOptRR(0, 0, 0, 1280, { }, { }));
EXPECT_CALL(server_, OnRequest("www.google.com", T_A))
.WillOnce(SetReply(&server_, &reply_badcookie))
.WillOnce(SetReply(&server_, &reply_badcookie))
.WillOnce(SetReply(&server_, &reply_badcookie))
.WillOnce(SetReply(&server_, &reply));
/* This test will establish the server supports cookies, but continuously
* returns BADCOOKIE which is an indicator that there is some form of
* AnyCast issue across servers, so it upgrades to TCP afterwards. No
* timeouts are recorded as the queries are sent back-to-back as immediate
* reattempts after the response. */
QueryResult result;
ares_query_dnsrec(channel_, "www.google.com", ARES_CLASS_IN, ARES_REC_TYPE_A, QueryCallback, &result, NULL);
Process();
EXPECT_TRUE(result.done_);
EXPECT_EQ(0, result.timeouts_);
}
#ifndef WIN32
TEST_P(MockChannelTest, HostAlias) {
DNSPacket reply;

@ -73,8 +73,8 @@ TEST_F(LibraryTest, ParsePtrReplyCname) {
struct DNSMalformedCnameRR : public DNSCnameRR {
DNSMalformedCnameRR(const std::string& name, int ttl, const std::string& other)
: DNSCnameRR(name, ttl, other) {}
std::vector<byte> data() const {
std::vector<byte> data = DNSRR::data();
std::vector<byte> data(const ares_dns_record_t *dnsrec) const {
std::vector<byte> data = DNSRR::data(dnsrec);
std::vector<byte> encname = EncodeString(other_);
encname[0] = encname[0] + 63; // invalid label length
int len = (int)encname.size();

55
test/ares-test.cc vendored

@ -596,7 +596,7 @@ void MockServer::ProcessPacket(ares_socket_t fd, struct sockaddr_storage *addr,
std::cerr << "ProcessRequest(" << qid << ", '" << name
<< "', " << RRTypeToString(rrtype) << ")" << std::endl;
}
ProcessRequest(fd, addr, addrlen, reqstr, qid, name, rrtype);
ProcessRequest(fd, addr, addrlen, req, reqstr, qid, name, rrtype);
ares_free_string(name);
}
@ -666,7 +666,8 @@ std::set<ares_socket_t> MockServer::fds() const {
}
void MockServer::ProcessRequest(ares_socket_t fd, struct sockaddr_storage* addr,
ares_socklen_t addrlen, const std::string &reqstr,
ares_socklen_t addrlen, const std::vector<byte> &req,
const std::string &reqstr,
int qid, const char *name, int rrtype) {
/* DNS 0x20 will mix case, do case-insensitive matching of name in request */
@ -683,7 +684,17 @@ void MockServer::ProcessRequest(ares_socket_t fd, struct sockaddr_storage* addr,
}
if (reply_ != nullptr) {
exact_reply_ = reply_->data(name);
ares_dns_record_t *dnsrec = NULL;
/* We will *attempt* to parse the request string. It may be malformed that
* will lead to a parse failure. If so, we just ignore it. We want to
* pass this parsed data structure to the reply generator in case it needs
* to extract metadata (such as a DNS client cookie) from the original
* request. If we can't parse it, oh well, we'll just pass NULL, most
* replies don't need anything from the request other than the name which
* is passed separately. */
ares_dns_parse(req.data(), req.size(), 0, &dnsrec);
exact_reply_ = reply_->data(name, dnsrec);
ares_dns_record_destroy(dnsrec);
}
if (exact_reply_.size() == 0) {
@ -1023,6 +1034,44 @@ void HostCallback(void *data, int status, int timeouts,
if (verbose) std::cerr << "HostCallback(" << *result << ")" << std::endl;
}
std::ostream& operator<<(std::ostream& os, const AresDnsRecord& dnsrec) {
os << "{'";
/* XXX: Todo */
os << '}';
return os;
}
std::ostream& operator<<(std::ostream& os, const QueryResult& result) {
os << '{';
if (result.done_) {
os << StatusToString(result.status_);
if (result.dnsrec_.dnsrec_ != nullptr) {
os << " " << result.dnsrec_;
} else {
os << ", (no dnsrec)";
}
} else {
os << "(incomplete)";
}
os << '}';
return os;
}
void QueryCallback(void *data, ares_status_t status, size_t timeouts,
const ares_dns_record_t *dnsrec) {
EXPECT_NE(nullptr, data);
if (data == nullptr)
return;
QueryResult* result = reinterpret_cast<QueryResult*>(data);
result->done_ = true;
result->status_ = status;
result->timeouts_ = timeouts;
if (dnsrec)
result->dnsrec_.SetDnsRecord(dnsrec);
if (verbose) std::cerr << "QueryCallback(" << *result << ")" << std::endl;
}
std::ostream& operator<<(std::ostream& os, const AddrInfoResult& result) {
os << '{';
if (result.done_ && result.ai_) {

51
test/ares-test.h vendored

@ -290,7 +290,8 @@ public:
private:
void ProcessRequest(ares_socket_t fd, struct sockaddr_storage *addr,
ares_socklen_t addrlen, const std::string &reqstr,
ares_socklen_t addrlen, const std::vector<byte> &req,
const std::string &reqstr,
int qid, const char *name, int rrtype);
void ProcessPacket(ares_socket_t fd, struct sockaddr_storage *addr,
ares_socklen_t addrlen, byte *data, int len);
@ -496,6 +497,52 @@ struct HostResult {
std::ostream &operator<<(std::ostream &os, const HostResult &result);
// C++ wrapper for ares_dns_record_t.
struct AresDnsRecord {
~AresDnsRecord()
{
ares_dns_record_destroy(dnsrec_);
dnsrec_ = NULL;
}
AresDnsRecord() : dnsrec_(NULL)
{
}
void SetDnsRecord(const ares_dns_record_t *dnsrec) {
if (dnsrec_ != NULL) {
ares_dns_record_destroy(dnsrec_);
}
if (dnsrec == NULL) {
return;
}
dnsrec_ = ares_dns_record_duplicate(dnsrec);
}
ares_dns_record_t *dnsrec_ = NULL;
};
std::ostream &operator<<(std::ostream &os, const AresDnsRecord &result);
// Structure that describes the result of an ares_host_callback invocation.
struct QueryResult {
QueryResult() : done_(false), status_(ARES_SUCCESS), timeouts_(0)
{
}
// Whether the callback has been invoked.
bool done_;
// Explicitly provided result information.
ares_status_t status_;
size_t timeouts_;
// Contents of the ares_dns_record_t structure if provided
AresDnsRecord dnsrec_;
};
std::ostream &operator<<(std::ostream &os, const QueryResult &result);
// Structure that describes the result of an ares_callback invocation.
struct SearchResult {
// Whether the callback has been invoked.
@ -556,6 +603,8 @@ std::ostream &operator<<(std::ostream &os, const AddrInfoResult &result);
// structures.
void HostCallback(void *data, int status, int timeouts,
struct hostent *hostent);
void QueryCallback(void *data, ares_status_t status, size_t timeouts,
const ares_dns_record_t *dnsrec);
void SearchCallback(void *data, int status, int timeouts, unsigned char *abuf,
int alen);
void SearchCallbackDnsRec(void *data, ares_status_t status, size_t timeouts,

101
test/dns-proto.cc vendored

@ -548,7 +548,7 @@ std::vector<byte> EncodeString(const std::string &name) {
return data;
}
std::vector<byte> DNSQuestion::data(const char *request_name) const {
std::vector<byte> DNSQuestion::data(const char *request_name, const ares_dns_record_t *dnsrec) const {
std::vector<byte> data;
std::vector<byte> encname;
if (request_name != nullptr && strcasecmp(request_name, name_.c_str()) == 0) {
@ -562,14 +562,14 @@ std::vector<byte> DNSQuestion::data(const char *request_name) const {
return data;
}
std::vector<byte> DNSRR::data() const {
std::vector<byte> data = DNSQuestion::data();
std::vector<byte> DNSRR::data(const ares_dns_record_t *dnsrec) const {
std::vector<byte> data = DNSQuestion::data(dnsrec);
PushInt32(&data, ttl_);
return data;
}
std::vector<byte> DNSSingleNameRR::data() const {
std::vector<byte> data = DNSRR::data();
std::vector<byte> DNSSingleNameRR::data(const ares_dns_record_t *dnsrec) const {
std::vector<byte> data = DNSRR::data(dnsrec);
std::vector<byte> encname = EncodeString(other_);
int len = (int)encname.size();
PushInt16(&data, len);
@ -577,8 +577,8 @@ std::vector<byte> DNSSingleNameRR::data() const {
return data;
}
std::vector<byte> DNSTxtRR::data() const {
std::vector<byte> data = DNSRR::data();
std::vector<byte> DNSTxtRR::data(const ares_dns_record_t *dnsrec) const {
std::vector<byte> data = DNSRR::data(dnsrec);
int len = 0;
for (const std::string& txt : txt_) {
len += (1 + (int)txt.size());
@ -591,8 +591,8 @@ std::vector<byte> DNSTxtRR::data() const {
return data;
}
std::vector<byte> DNSMxRR::data() const {
std::vector<byte> data = DNSRR::data();
std::vector<byte> DNSMxRR::data(const ares_dns_record_t *dnsrec) const {
std::vector<byte> data = DNSRR::data(dnsrec);
std::vector<byte> encname = EncodeString(other_);
int len = 2 + (int)encname.size();
PushInt16(&data, len);
@ -601,8 +601,8 @@ std::vector<byte> DNSMxRR::data() const {
return data;
}
std::vector<byte> DNSSrvRR::data() const {
std::vector<byte> data = DNSRR::data();
std::vector<byte> DNSSrvRR::data(const ares_dns_record_t *dnsrec) const {
std::vector<byte> data = DNSRR::data(dnsrec);
std::vector<byte> encname = EncodeString(target_);
int len = 6 + (int)encname.size();
PushInt16(&data, len);
@ -613,8 +613,8 @@ std::vector<byte> DNSSrvRR::data() const {
return data;
}
std::vector<byte> DNSUriRR::data() const {
std::vector<byte> data = DNSRR::data();
std::vector<byte> DNSUriRR::data(const ares_dns_record_t *dnsrec) const {
std::vector<byte> data = DNSRR::data(dnsrec);
int len = 4 + (int)target_.size();
PushInt16(&data, len);
PushInt16(&data, prio_);
@ -623,16 +623,16 @@ std::vector<byte> DNSUriRR::data() const {
return data;
}
std::vector<byte> DNSAddressRR::data() const {
std::vector<byte> data = DNSRR::data();
std::vector<byte> DNSAddressRR::data(const ares_dns_record_t *dnsrec) const {
std::vector<byte> data = DNSRR::data(dnsrec);
int len = (int)addr_.size();
PushInt16(&data, len);
data.insert(data.end(), addr_.begin(), addr_.end());
return data;
}
std::vector<byte> DNSSoaRR::data() const {
std::vector<byte> data = DNSRR::data();
std::vector<byte> DNSSoaRR::data(const ares_dns_record_t *dnsrec) const {
std::vector<byte> data = DNSRR::data(dnsrec);
std::vector<byte> encname1 = EncodeString(nsname_);
std::vector<byte> encname2 = EncodeString(rname_);
int len = (int)encname1.size() + (int)encname2.size() + 5*4;
@ -647,23 +647,70 @@ std::vector<byte> DNSSoaRR::data() const {
return data;
}
std::vector<byte> DNSOptRR::data() const {
std::vector<byte> data = DNSRR::data();
int len = 0;
const ares_dns_rr_t *fetch_rr_opt(const ares_dns_record_t *rec)
{
size_t i;
for (i = 0; i < ares_dns_record_rr_cnt(rec, ARES_SECTION_ADDITIONAL); i++) {
const ares_dns_rr_t *rr =
ares_dns_record_rr_get_const(rec, ARES_SECTION_ADDITIONAL, i);
if (ares_dns_rr_get_type(rr) == ARES_REC_TYPE_OPT) {
return rr;
}
}
return NULL;
}
std::vector<byte> DNSOptRR::data(const ares_dns_record_t *dnsrec) const {
std::vector<byte> data = DNSRR::data(dnsrec);
int len = 0;
std::vector<byte> cookie;
/* See if we should be applying a server cookie */
if (server_cookie_.size()) {
const ares_dns_rr_t *rr = fetch_rr_opt(dnsrec);
const unsigned char *val = NULL;
size_t len = 0;
if (ares_dns_rr_get_opt_byid(rr, ARES_RR_OPT_OPTIONS, ARES_OPT_PARAM_COOKIE,
&val, &len)) {
/* If client cookie was provided to test framework, we are overwriting
* the one received from the client. This is likely to test failure
* scenarios */
if (client_cookie_.size()) {
cookie.insert(cookie.end(), client_cookie_.begin(), client_cookie_.end());
} else {
cookie.insert(cookie.end(), val, val+8);
}
cookie.insert(cookie.end(), server_cookie_.begin(), server_cookie_.end());
}
}
if (cookie.size()) {
len += 4 + (int)cookie.size();
}
for (const DNSOption& opt : opts_) {
len += (4 + (int)opt.data_.size());
}
PushInt16(&data, len);
for (const DNSOption& opt : opts_) {
PushInt16(&data, opt.code_);
PushInt16(&data, (int)opt.data_.size());
data.insert(data.end(), opt.data_.begin(), opt.data_.end());
}
if (cookie.size()) {
PushInt16(&data, ARES_OPT_PARAM_COOKIE);
PushInt16(&data, (int)cookie.size());
data.insert(data.end(), cookie.begin(), cookie.end());
}
return data;
}
std::vector<byte> DNSNaptrRR::data() const {
std::vector<byte> data = DNSRR::data();
std::vector<byte> DNSNaptrRR::data(const ares_dns_record_t *dnsrec) const {
std::vector<byte> data = DNSRR::data(dnsrec);
std::vector<byte> encname = EncodeString(replacement_);
int len = (4 + 1 + (int)flags_.size() + 1 + (int)service_.size() + 1 + (int)regexp_.size() + (int)encname.size());
PushInt16(&data, len);
@ -679,7 +726,7 @@ std::vector<byte> DNSNaptrRR::data() const {
return data;
}
std::vector<byte> DNSPacket::data(const char *request_name) const {
std::vector<byte> DNSPacket::data(const char *request_name, const ares_dns_record_t *dnsrec) const {
std::vector<byte> data;
PushInt16(&data, qid_);
byte b = 0x00;
@ -707,19 +754,19 @@ std::vector<byte> DNSPacket::data(const char *request_name) const {
PushInt16(&data, count);
for (const std::unique_ptr<DNSQuestion>& question : questions_) {
std::vector<byte> qdata = question->data(request_name);
std::vector<byte> qdata = question->data(request_name, dnsrec);
data.insert(data.end(), qdata.begin(), qdata.end());
}
for (const std::unique_ptr<DNSRR>& rr : answers_) {
std::vector<byte> rrdata = rr->data();
std::vector<byte> rrdata = rr->data(dnsrec);
data.insert(data.end(), rrdata.begin(), rrdata.end());
}
for (const std::unique_ptr<DNSRR>& rr : auths_) {
std::vector<byte> rrdata = rr->data();
std::vector<byte> rrdata = rr->data(dnsrec);
data.insert(data.end(), rrdata.begin(), rrdata.end());
}
for (const std::unique_ptr<DNSRR>& rr : adds_) {
std::vector<byte> rrdata = rr->data();
std::vector<byte> rrdata = rr->data(dnsrec);
data.insert(data.end(), rrdata.begin(), rrdata.end());
}
return data;

43
test/dns-proto.h vendored

@ -53,6 +53,8 @@ std::string RRTypeToString(int rrtype);
std::string ClassToString(int qclass);
std::string AddressToString(const void *addr, int len);
const ares_dns_rr_t *fetch_rr_opt(const ares_dns_record_t *rec);
// Convert DNS protocol data to strings.
// Note that these functions are not defensive; they assume
// a validly formatted input, and so should not be used on
@ -84,11 +86,16 @@ struct DNSQuestion {
{
}
virtual std::vector<byte> data(const char *request_name) const;
virtual std::vector<byte> data(const char *request_name, const ares_dns_record_t *dnsrec) const;
virtual std::vector<byte> data(const ares_dns_record_t *dnsrec) const
{
return data(nullptr, dnsrec);
}
virtual std::vector<byte> data() const
{
return data(nullptr);
return data(nullptr, nullptr);
}
std::string name_;
@ -111,7 +118,7 @@ struct DNSRR : public DNSQuestion {
{
}
virtual std::vector<byte> data() const = 0;
virtual std::vector<byte> data(const ares_dns_record_t *dnsrec) const = 0;
int ttl_;
};
@ -128,7 +135,7 @@ struct DNSAddressRR : public DNSRR {
{
}
virtual std::vector<byte> data() const;
virtual std::vector<byte> data(const ares_dns_record_t *dnsrec) const;
std::vector<byte> addr_;
};
@ -163,7 +170,7 @@ struct DNSSingleNameRR : public DNSRR {
{
}
virtual std::vector<byte> data() const;
virtual std::vector<byte> data(const ares_dns_record_t *dnsrec) const;
std::string other_;
};
@ -195,7 +202,7 @@ struct DNSTxtRR : public DNSRR {
{
}
virtual std::vector<byte> data() const;
virtual std::vector<byte> data(const ares_dns_record_t *dnsrec) const;
std::vector<std::string> txt_;
};
@ -205,7 +212,7 @@ struct DNSMxRR : public DNSRR {
{
}
virtual std::vector<byte> data() const;
virtual std::vector<byte> data(const ares_dns_record_t *dnsrec) const;
int pref_;
std::string other_;
};
@ -218,7 +225,7 @@ struct DNSSrvRR : public DNSRR {
{
}
virtual std::vector<byte> data() const;
virtual std::vector<byte> data(const ares_dns_record_t *dnsrec) const;
int prio_;
int weight_;
int port_;
@ -232,7 +239,7 @@ struct DNSUriRR : public DNSRR {
{
}
virtual std::vector<byte> data() const;
virtual std::vector<byte> data(const ares_dns_record_t *dnsrec) const;
int prio_;
int weight_;
std::string target_;
@ -247,7 +254,7 @@ struct DNSSoaRR : public DNSRR {
{
}
virtual std::vector<byte> data() const;
virtual std::vector<byte> data(const ares_dns_record_t *dnsrec) const;
std::string nsname_;
std::string rname_;
int serial_;
@ -266,7 +273,7 @@ struct DNSNaptrRR : public DNSRR {
{
}
virtual std::vector<byte> data() const;
virtual std::vector<byte> data(const ares_dns_record_t *dnsrec) const;
int order_;
int pref_;
std::string flags_;
@ -281,13 +288,17 @@ struct DNSOption {
};
struct DNSOptRR : public DNSRR {
DNSOptRR(int extrcode, int udpsize)
: DNSRR("", T_OPT, static_cast<int>(udpsize), extrcode)
DNSOptRR(unsigned char extrcode, unsigned char version, unsigned short flags, int udpsize, std::vector<byte> client_cookie, std::vector<byte> server_cookie)
: DNSRR("", T_OPT, static_cast<int>(udpsize), ((int)extrcode) << 24 | ((int)version) << 16 | ((int)flags)/* ttl */)
{
client_cookie_ = client_cookie;
server_cookie_ = server_cookie;
}
virtual std::vector<byte> data() const;
virtual std::vector<byte> data(const ares_dns_record_t *dnsrec) const;
std::vector<DNSOption> opts_;
std::vector<byte> client_cookie_;
std::vector<byte> server_cookie_;
};
struct DNSPacket {
@ -384,11 +395,11 @@ struct DNSPacket {
}
// Return the encoded packet.
std::vector<byte> data(const char *request_name) const;
std::vector<byte> data(const char *request_name, const ares_dns_record_t *dnsrec) const;
std::vector<byte> data() const
{
return data(nullptr);
return data(nullptr, nullptr);
}
int qid_;

Loading…
Cancel
Save