mirror of
https://sourceware.org/git/glibc.git
synced 2025-07-28 00:21:52 +03:00
resolv: Reduce EDNS payload size to 1200 bytes [BZ #21361]
This hardens the stub resolver against fragmentation-based attacks.
This commit is contained in:
21
ChangeLog
21
ChangeLog
@ -1,3 +1,24 @@
|
|||||||
|
2017-04-13 Florian Weimer <fweimer@redhat.com>
|
||||||
|
|
||||||
|
[BZ #21361]
|
||||||
|
Limit EDNS buffer size to 1200 bytes.
|
||||||
|
* include/resolv.h (__res_nopt): Remove declaration.
|
||||||
|
* resolv/Makefile (tests): tst-resolv-edns.
|
||||||
|
(tst-resolv-edns): Link with -lresolv, -lpthread.
|
||||||
|
* resolv/res_mkquery.c (__res_ntop): Limit EDNS buffer size to the
|
||||||
|
interval [512, 1200].
|
||||||
|
* resolv/res_query.c (__libc_res_nquery): Use 1200 buffer size if
|
||||||
|
we can resize the buffer.
|
||||||
|
* resolv/resolv-internal.h (RESOLV_EDNS_BUFFER_SIZE): Define.
|
||||||
|
(__res_nopt): Declare.
|
||||||
|
* resolv/tst-resolv-edns.c: New file.
|
||||||
|
* resolv/resolv_test.h (struct resolv_edns_info): Define.
|
||||||
|
(struct resolv_response_context): Add edns member.
|
||||||
|
* resolv/resolv_test.c (struct query_info): Add edns member.
|
||||||
|
(parse_query): Extract EDNS information from the query.
|
||||||
|
(server_thread_udp_process_one): Propagate EDNS data.
|
||||||
|
(server_thread_tcp_client): Likewise.
|
||||||
|
|
||||||
2017-04-13 Florian Weimer <fweimer@redhat.com>
|
2017-04-13 Florian Weimer <fweimer@redhat.com>
|
||||||
|
|
||||||
[BZ #21359]
|
[BZ #21359]
|
||||||
|
3
NEWS
3
NEWS
@ -46,7 +46,8 @@ Version 2.26
|
|||||||
|
|
||||||
Security related changes:
|
Security related changes:
|
||||||
|
|
||||||
[Add security related changes here]
|
* The DNS stub resolver limits the advertised UDP buffer size to 1200 bytes,
|
||||||
|
to avoid fragmentation-based spoofing attacks.
|
||||||
|
|
||||||
The following bugs are resolved with this release:
|
The following bugs are resolved with this release:
|
||||||
|
|
||||||
|
@ -37,8 +37,6 @@ extern void res_pquery (const res_state __statp, const unsigned char *__msg,
|
|||||||
extern int res_ourserver_p (const res_state __statp,
|
extern int res_ourserver_p (const res_state __statp,
|
||||||
const struct sockaddr_in6 *__inp);
|
const struct sockaddr_in6 *__inp);
|
||||||
extern void __res_iclose (res_state statp, bool free_addr);
|
extern void __res_iclose (res_state statp, bool free_addr);
|
||||||
extern int __res_nopt(res_state statp, int n0, unsigned char *buf, int buflen,
|
|
||||||
int anslen);
|
|
||||||
libc_hidden_proto (__res_ninit)
|
libc_hidden_proto (__res_ninit)
|
||||||
libc_hidden_proto (__res_maybe_init)
|
libc_hidden_proto (__res_maybe_init)
|
||||||
libc_hidden_proto (__res_nclose)
|
libc_hidden_proto (__res_nclose)
|
||||||
@ -91,7 +89,6 @@ libresolv_hidden_proto (__res_nameinquery)
|
|||||||
libresolv_hidden_proto (__res_queriesmatch)
|
libresolv_hidden_proto (__res_queriesmatch)
|
||||||
libresolv_hidden_proto (__res_nsend)
|
libresolv_hidden_proto (__res_nsend)
|
||||||
libresolv_hidden_proto (__b64_ntop)
|
libresolv_hidden_proto (__b64_ntop)
|
||||||
libresolv_hidden_proto (__res_nopt)
|
|
||||||
libresolv_hidden_proto (__dn_count_labels)
|
libresolv_hidden_proto (__dn_count_labels)
|
||||||
libresolv_hidden_proto (__p_secstodate)
|
libresolv_hidden_proto (__p_secstodate)
|
||||||
|
|
||||||
|
@ -48,6 +48,7 @@ tests += \
|
|||||||
tst-res_hconf_reorder \
|
tst-res_hconf_reorder \
|
||||||
tst-res_use_inet6 \
|
tst-res_use_inet6 \
|
||||||
tst-resolv-basic \
|
tst-resolv-basic \
|
||||||
|
tst-resolv-edns \
|
||||||
tst-resolv-network \
|
tst-resolv-network \
|
||||||
tst-resolv-search \
|
tst-resolv-search \
|
||||||
|
|
||||||
@ -133,6 +134,7 @@ $(objpfx)tst-bug18665-tcp: $(objpfx)libresolv.so $(shared-thread-library)
|
|||||||
$(objpfx)tst-bug18665: $(objpfx)libresolv.so $(shared-thread-library)
|
$(objpfx)tst-bug18665: $(objpfx)libresolv.so $(shared-thread-library)
|
||||||
$(objpfx)tst-res_use_inet6: $(objpfx)libresolv.so $(shared-thread-library)
|
$(objpfx)tst-res_use_inet6: $(objpfx)libresolv.so $(shared-thread-library)
|
||||||
$(objpfx)tst-resolv-basic: $(objpfx)libresolv.so $(shared-thread-library)
|
$(objpfx)tst-resolv-basic: $(objpfx)libresolv.so $(shared-thread-library)
|
||||||
|
$(objpfx)tst-resolv-edns: $(objpfx)libresolv.so $(shared-thread-library)
|
||||||
$(objpfx)tst-resolv-network: $(objpfx)libresolv.so $(shared-thread-library)
|
$(objpfx)tst-resolv-network: $(objpfx)libresolv.so $(shared-thread-library)
|
||||||
$(objpfx)tst-resolv-qtypes: $(objpfx)libresolv.so $(shared-thread-library)
|
$(objpfx)tst-resolv-qtypes: $(objpfx)libresolv.so $(shared-thread-library)
|
||||||
$(objpfx)tst-resolv-search: $(objpfx)libresolv.so $(shared-thread-library)
|
$(objpfx)tst-resolv-search: $(objpfx)libresolv.so $(shared-thread-library)
|
||||||
|
@ -69,7 +69,7 @@
|
|||||||
#include <netinet/in.h>
|
#include <netinet/in.h>
|
||||||
#include <arpa/nameser.h>
|
#include <arpa/nameser.h>
|
||||||
#include <netdb.h>
|
#include <netdb.h>
|
||||||
#include <resolv.h>
|
#include <resolv/resolv-internal.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <sys/time.h>
|
#include <sys/time.h>
|
||||||
@ -225,7 +225,30 @@ __res_nopt(res_state statp,
|
|||||||
*cp++ = 0; /* "." */
|
*cp++ = 0; /* "." */
|
||||||
|
|
||||||
NS_PUT16(T_OPT, cp); /* TYPE */
|
NS_PUT16(T_OPT, cp); /* TYPE */
|
||||||
NS_PUT16(MIN(anslen, 0xffff), cp); /* CLASS = UDP payload size */
|
|
||||||
|
/* Lowering the advertised buffer size based on the actual
|
||||||
|
answer buffer size is desirable because the server will
|
||||||
|
minimize the reply to fit into the UDP packet (and A
|
||||||
|
non-minimal response might not fit the buffer).
|
||||||
|
|
||||||
|
The RESOLV_EDNS_BUFFER_SIZE limit could still result in TCP
|
||||||
|
fallback and a non-minimal response which has to be
|
||||||
|
hard-truncated in the stub resolver, but this is price to
|
||||||
|
pay for avoiding fragmentation. (This issue does not
|
||||||
|
affect the nss_dns functions because they use the stub
|
||||||
|
resolver in such a way that it allocates a properly sized
|
||||||
|
response buffer.) */
|
||||||
|
{
|
||||||
|
uint16_t buffer_size;
|
||||||
|
if (anslen < 512)
|
||||||
|
buffer_size = 512;
|
||||||
|
else if (anslen > RESOLV_EDNS_BUFFER_SIZE)
|
||||||
|
buffer_size = RESOLV_EDNS_BUFFER_SIZE;
|
||||||
|
else
|
||||||
|
buffer_size = anslen;
|
||||||
|
NS_PUT16 (buffer_size, cp);
|
||||||
|
}
|
||||||
|
|
||||||
*cp++ = NOERROR; /* extended RCODE */
|
*cp++ = NOERROR; /* extended RCODE */
|
||||||
*cp++ = 0; /* EDNS version */
|
*cp++ = 0; /* EDNS version */
|
||||||
|
|
||||||
@ -243,4 +266,3 @@ __res_nopt(res_state statp,
|
|||||||
|
|
||||||
return cp - buf;
|
return cp - buf;
|
||||||
}
|
}
|
||||||
libresolv_hidden_def (__res_nopt)
|
|
||||||
|
@ -78,6 +78,7 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <resolv/resolv-internal.h>
|
||||||
|
|
||||||
/* Options. Leave them on. */
|
/* Options. Leave them on. */
|
||||||
/* #undef DEBUG */
|
/* #undef DEBUG */
|
||||||
@ -147,7 +148,10 @@ __libc_res_nquery(res_state statp,
|
|||||||
if ((oflags & RES_F_EDNS0ERR) == 0
|
if ((oflags & RES_F_EDNS0ERR) == 0
|
||||||
&& (statp->options & (RES_USE_EDNS0|RES_USE_DNSSEC)) != 0)
|
&& (statp->options & (RES_USE_EDNS0|RES_USE_DNSSEC)) != 0)
|
||||||
{
|
{
|
||||||
n = __res_nopt(statp, n, query1, bufsize, anslen / 2);
|
/* Use RESOLV_EDNS_BUFFER_SIZE because the receive
|
||||||
|
buffer can be reallocated. */
|
||||||
|
n = __res_nopt (statp, n, query1, bufsize,
|
||||||
|
RESOLV_EDNS_BUFFER_SIZE);
|
||||||
if (n < 0)
|
if (n < 0)
|
||||||
goto unspec_nomem;
|
goto unspec_nomem;
|
||||||
}
|
}
|
||||||
@ -168,8 +172,10 @@ __libc_res_nquery(res_state statp,
|
|||||||
if (n > 0
|
if (n > 0
|
||||||
&& (oflags & RES_F_EDNS0ERR) == 0
|
&& (oflags & RES_F_EDNS0ERR) == 0
|
||||||
&& (statp->options & (RES_USE_EDNS0|RES_USE_DNSSEC)) != 0)
|
&& (statp->options & (RES_USE_EDNS0|RES_USE_DNSSEC)) != 0)
|
||||||
n = __res_nopt(statp, n, query2, bufsize - nused - n,
|
/* Use RESOLV_EDNS_BUFFER_SIZE because the receive
|
||||||
anslen / 2);
|
buffer can be reallocated. */
|
||||||
|
n = __res_nopt (statp, n, query2, bufsize,
|
||||||
|
RESOLV_EDNS_BUFFER_SIZE);
|
||||||
nquery2 = n;
|
nquery2 = n;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,7 +189,16 @@ __libc_res_nquery(res_state statp,
|
|||||||
if (n > 0
|
if (n > 0
|
||||||
&& (oflags & RES_F_EDNS0ERR) == 0
|
&& (oflags & RES_F_EDNS0ERR) == 0
|
||||||
&& (statp->options & (RES_USE_EDNS0|RES_USE_DNSSEC)) != 0)
|
&& (statp->options & (RES_USE_EDNS0|RES_USE_DNSSEC)) != 0)
|
||||||
n = __res_nopt(statp, n, query1, bufsize, anslen);
|
{
|
||||||
|
/* Use RESOLV_EDNS_BUFFER_SIZE if the receive buffer
|
||||||
|
can be reallocated. */
|
||||||
|
size_t advertise;
|
||||||
|
if (answerp == NULL)
|
||||||
|
advertise = anslen;
|
||||||
|
else
|
||||||
|
advertise = RESOLV_EDNS_BUFFER_SIZE;
|
||||||
|
n = __res_nopt (statp, n, query1, bufsize, advertise);
|
||||||
|
}
|
||||||
|
|
||||||
nquery1 = n;
|
nquery1 = n;
|
||||||
}
|
}
|
||||||
|
@ -38,4 +38,22 @@ res_use_inet6 (void)
|
|||||||
return _res.options & DEPRECATED_RES_USE_INET6;
|
return _res.options & DEPRECATED_RES_USE_INET6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
/* The advertized EDNS buffer size. The value 1200 is derived
|
||||||
|
from the IPv6 minimum MTU (1280 bytes) minus some arbitrary
|
||||||
|
space for tunneling overhead. If the DNS server does not react
|
||||||
|
to ICMP Fragmentation Needed But DF Set messages, this should
|
||||||
|
avoid all UDP fragments on current networks. Avoiding UDP
|
||||||
|
fragments is desirable because it prevents fragmentation-based
|
||||||
|
spoofing attacks because the randomness in a DNS packet is
|
||||||
|
concentrated in the first fragment (with the headers) and does
|
||||||
|
not protect subsequent fragments. */
|
||||||
|
RESOLV_EDNS_BUFFER_SIZE = 1200,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Add an OPT record to a DNS query. */
|
||||||
|
int __res_nopt (res_state, int n0, unsigned char *buf, int buflen,
|
||||||
|
int anslen) attribute_hidden;
|
||||||
|
|
||||||
#endif /* _RESOLV_INTERNAL_H */
|
#endif /* _RESOLV_INTERNAL_H */
|
||||||
|
501
resolv/tst-resolv-edns.c
Normal file
501
resolv/tst-resolv-edns.c
Normal file
@ -0,0 +1,501 @@
|
|||||||
|
/* Test EDNS handling in the stub resolver.
|
||||||
|
Copyright (C) 2016-2017 Free Software Foundation, Inc.
|
||||||
|
This file is part of the GNU C Library.
|
||||||
|
|
||||||
|
The GNU C Library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
The GNU C Library is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with the GNU C Library; if not, see
|
||||||
|
<http://www.gnu.org/licenses/>. */
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <netdb.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <support/check.h>
|
||||||
|
#include <support/resolv_test.h>
|
||||||
|
#include <support/support.h>
|
||||||
|
#include <support/test-driver.h>
|
||||||
|
#include <support/xthread.h>
|
||||||
|
|
||||||
|
/* Data produced by a test query. */
|
||||||
|
struct response_data
|
||||||
|
{
|
||||||
|
char *qname;
|
||||||
|
uint16_t qtype;
|
||||||
|
struct resolv_edns_info edns;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Global array used by put_response and get_response to record
|
||||||
|
response data. The test DNS server returns the index of the array
|
||||||
|
element which contains the actual response data. This enables the
|
||||||
|
test case to return arbitrary amounts of data with the limited
|
||||||
|
number of bits which fit into an IP addres.
|
||||||
|
|
||||||
|
The volatile specifier is needed because the test case accesses
|
||||||
|
these variables from a callback function called from a function
|
||||||
|
which is marked as __THROW (i.e., a leaf function which actually is
|
||||||
|
not). */
|
||||||
|
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||||
|
static struct response_data ** volatile response_data_array;
|
||||||
|
volatile static size_t response_data_count;
|
||||||
|
|
||||||
|
/* Extract information from the query, store it in a struct
|
||||||
|
response_data object, and return its index in the
|
||||||
|
response_data_array. */
|
||||||
|
static unsigned int
|
||||||
|
put_response (const struct resolv_response_context *ctx,
|
||||||
|
const char *qname, uint16_t qtype)
|
||||||
|
{
|
||||||
|
xpthread_mutex_lock (&mutex);
|
||||||
|
++response_data_count;
|
||||||
|
/* We only can represent 2**24 indexes in 10.0.0.0/8. */
|
||||||
|
TEST_VERIFY (response_data_count < (1 << 24));
|
||||||
|
response_data_array = xrealloc
|
||||||
|
(response_data_array, sizeof (*response_data_array) * response_data_count);
|
||||||
|
unsigned int index = response_data_count - 1;
|
||||||
|
struct response_data *data = xmalloc (sizeof (*data));
|
||||||
|
*data = (struct response_data)
|
||||||
|
{
|
||||||
|
.qname = xstrdup (qname),
|
||||||
|
.qtype = qtype,
|
||||||
|
.edns = ctx->edns,
|
||||||
|
};
|
||||||
|
response_data_array[index] = data;
|
||||||
|
xpthread_mutex_unlock (&mutex);
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Verify the index into the response_data array and return the data
|
||||||
|
at it. */
|
||||||
|
static struct response_data *
|
||||||
|
get_response (unsigned int index)
|
||||||
|
{
|
||||||
|
xpthread_mutex_lock (&mutex);
|
||||||
|
TEST_VERIFY_EXIT (index < response_data_count);
|
||||||
|
struct response_data *result = response_data_array[index];
|
||||||
|
xpthread_mutex_unlock (&mutex);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Deallocate all response data. */
|
||||||
|
static void
|
||||||
|
free_response_data (void)
|
||||||
|
{
|
||||||
|
xpthread_mutex_lock (&mutex);
|
||||||
|
size_t count = response_data_count;
|
||||||
|
struct response_data **array = response_data_array;
|
||||||
|
for (unsigned int i = 0; i < count; ++i)
|
||||||
|
{
|
||||||
|
struct response_data *data = array[i];
|
||||||
|
free (data->qname);
|
||||||
|
free (data);
|
||||||
|
}
|
||||||
|
free (array);
|
||||||
|
response_data_array = NULL;
|
||||||
|
response_data_count = 0;
|
||||||
|
xpthread_mutex_unlock (&mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define EDNS_PROBE_EXAMPLE "edns-probe.example"
|
||||||
|
|
||||||
|
static void
|
||||||
|
response (const struct resolv_response_context *ctx,
|
||||||
|
struct resolv_response_builder *b,
|
||||||
|
const char *qname, uint16_t qclass, uint16_t qtype)
|
||||||
|
{
|
||||||
|
TEST_VERIFY_EXIT (qname != NULL);
|
||||||
|
|
||||||
|
/* The "tcp." prefix can be used to request TCP fallback. */
|
||||||
|
const char *qname_compare = qname;
|
||||||
|
bool force_tcp;
|
||||||
|
if (strncmp ("tcp.", qname_compare, strlen ("tcp.")) == 0)
|
||||||
|
{
|
||||||
|
force_tcp = true;
|
||||||
|
qname_compare += strlen ("tcp.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
force_tcp = false;
|
||||||
|
|
||||||
|
enum {edns_probe} requested_qname;
|
||||||
|
if (strcmp (qname_compare, EDNS_PROBE_EXAMPLE) == 0)
|
||||||
|
requested_qname = edns_probe;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
support_record_failure ();
|
||||||
|
printf ("error: unexpected QNAME: %s\n", qname);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
TEST_VERIFY_EXIT (qclass == C_IN);
|
||||||
|
struct resolv_response_flags flags = {.tc = force_tcp && !ctx->tcp};
|
||||||
|
resolv_response_init (b, flags);
|
||||||
|
resolv_response_add_question (b, qname, qclass, qtype);
|
||||||
|
if (flags.tc)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (test_verbose)
|
||||||
|
printf ("info: edns=%d payload_size=%d\n",
|
||||||
|
ctx->edns.active, ctx->edns.payload_size);
|
||||||
|
|
||||||
|
/* Encode the response_data object in multiple address records.
|
||||||
|
Each record carries two bytes of payload data, and an index. */
|
||||||
|
resolv_response_section (b, ns_s_an);
|
||||||
|
switch (requested_qname)
|
||||||
|
{
|
||||||
|
case edns_probe:
|
||||||
|
{
|
||||||
|
unsigned int index = put_response (ctx, qname, qtype);
|
||||||
|
switch (qtype)
|
||||||
|
{
|
||||||
|
case T_A:
|
||||||
|
{
|
||||||
|
uint32_t addr = htonl (0x0a000000 | index);
|
||||||
|
resolv_response_open_record (b, qname, qclass, qtype, 0);
|
||||||
|
resolv_response_add_data (b, &addr, sizeof (addr));
|
||||||
|
resolv_response_close_record (b);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case T_AAAA:
|
||||||
|
{
|
||||||
|
char addr[16]
|
||||||
|
= {0x20, 0x01, 0xd, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
index >> 16, index >> 8, index};
|
||||||
|
resolv_response_open_record (b, qname, qclass, qtype, 0);
|
||||||
|
resolv_response_add_data (b, &addr, sizeof (addr));
|
||||||
|
resolv_response_close_record (b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update *DATA with data from ADDRESS of SIZE. Set the corresponding
|
||||||
|
flag in SHADOW for each byte written. */
|
||||||
|
static struct response_data *
|
||||||
|
decode_address (const void *address, size_t size)
|
||||||
|
{
|
||||||
|
switch (size)
|
||||||
|
{
|
||||||
|
case 4:
|
||||||
|
TEST_VERIFY (memcmp (address, "\x0a", 1) == 0);
|
||||||
|
break;
|
||||||
|
case 16:
|
||||||
|
TEST_VERIFY (memcmp (address, "\x20\x01\x0d\xb8", 4) == 0);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
FAIL_EXIT1 ("unexpected address size %zu", size);
|
||||||
|
}
|
||||||
|
const unsigned char *addr = address;
|
||||||
|
unsigned int index = addr[size - 3] * 256 * 256
|
||||||
|
+ addr[size - 2] * 256
|
||||||
|
+ addr[size - 1];
|
||||||
|
return get_response (index);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct response_data *
|
||||||
|
decode_hostent (struct hostent *e)
|
||||||
|
{
|
||||||
|
TEST_VERIFY_EXIT (e != NULL);
|
||||||
|
TEST_VERIFY_EXIT (e->h_addr_list[0] != NULL);
|
||||||
|
TEST_VERIFY (e->h_addr_list[1] == NULL);
|
||||||
|
return decode_address (e->h_addr_list[0], e->h_length);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct response_data *
|
||||||
|
decode_addrinfo (struct addrinfo *ai, int family)
|
||||||
|
{
|
||||||
|
struct response_data *data = NULL;
|
||||||
|
while (ai != NULL)
|
||||||
|
{
|
||||||
|
if (ai->ai_family == family)
|
||||||
|
{
|
||||||
|
struct response_data *new_data;
|
||||||
|
switch (family)
|
||||||
|
{
|
||||||
|
case AF_INET:
|
||||||
|
{
|
||||||
|
struct sockaddr_in *pin = (struct sockaddr_in *) ai->ai_addr;
|
||||||
|
new_data = decode_address (&pin->sin_addr.s_addr, 4);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case AF_INET6:
|
||||||
|
{
|
||||||
|
struct sockaddr_in6 *pin = (struct sockaddr_in6 *) ai->ai_addr;
|
||||||
|
new_data = decode_address (&pin->sin6_addr.s6_addr, 16);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
FAIL_EXIT1 ("invalid address family %d", ai->ai_family);
|
||||||
|
}
|
||||||
|
if (data == NULL)
|
||||||
|
data = new_data;
|
||||||
|
else
|
||||||
|
/* Check pointer equality because this should be the same
|
||||||
|
response (same index). */
|
||||||
|
TEST_VERIFY (data == new_data);
|
||||||
|
}
|
||||||
|
ai = ai->ai_next;
|
||||||
|
}
|
||||||
|
TEST_VERIFY_EXIT (data != NULL);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Updated by the main test loop in accordance with what is set in
|
||||||
|
_res.options. */
|
||||||
|
static bool use_edns;
|
||||||
|
static bool use_dnssec;
|
||||||
|
|
||||||
|
/* Verify the decoded response data against the flags above. */
|
||||||
|
static void
|
||||||
|
verify_response_data_payload (struct response_data *data,
|
||||||
|
size_t expected_payload)
|
||||||
|
{
|
||||||
|
bool edns = use_edns || use_dnssec;
|
||||||
|
TEST_VERIFY (data->edns.active == edns);
|
||||||
|
if (!edns)
|
||||||
|
expected_payload = 0;
|
||||||
|
if (data->edns.payload_size != expected_payload)
|
||||||
|
{
|
||||||
|
support_record_failure ();
|
||||||
|
printf ("error: unexpected payload size %d (edns=%d)\n",
|
||||||
|
(int) data->edns.payload_size, edns);
|
||||||
|
}
|
||||||
|
uint16_t expected_flags = 0;
|
||||||
|
if (use_dnssec)
|
||||||
|
expected_flags |= 0x8000; /* DO flag. */
|
||||||
|
if (data->edns.flags != expected_flags)
|
||||||
|
{
|
||||||
|
support_record_failure ();
|
||||||
|
printf ("error: unexpected EDNS flags 0x%04x (edns=%d)\n",
|
||||||
|
(int) data->edns.flags, edns);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Same as verify_response_data_payload, but use the default
|
||||||
|
payload. */
|
||||||
|
static void
|
||||||
|
verify_response_data (struct response_data *data)
|
||||||
|
{
|
||||||
|
verify_response_data_payload (data, 1200);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
check_hostent (struct hostent *e)
|
||||||
|
{
|
||||||
|
TEST_VERIFY_EXIT (e != NULL);
|
||||||
|
verify_response_data (decode_hostent (e));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
do_ai (int family)
|
||||||
|
{
|
||||||
|
struct addrinfo hints = { .ai_family = family };
|
||||||
|
struct addrinfo *ai;
|
||||||
|
int ret = getaddrinfo (EDNS_PROBE_EXAMPLE, "80", &hints, &ai);
|
||||||
|
TEST_VERIFY_EXIT (ret == 0);
|
||||||
|
switch (family)
|
||||||
|
{
|
||||||
|
case AF_INET:
|
||||||
|
case AF_INET6:
|
||||||
|
verify_response_data (decode_addrinfo (ai, family));
|
||||||
|
break;
|
||||||
|
case AF_UNSPEC:
|
||||||
|
verify_response_data (decode_addrinfo (ai, AF_INET));
|
||||||
|
verify_response_data (decode_addrinfo (ai, AF_INET6));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
FAIL_EXIT1 ("invalid address family %d", family);
|
||||||
|
}
|
||||||
|
freeaddrinfo (ai);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum res_op
|
||||||
|
{
|
||||||
|
res_op_search,
|
||||||
|
res_op_query,
|
||||||
|
res_op_querydomain,
|
||||||
|
res_op_nsearch,
|
||||||
|
res_op_nquery,
|
||||||
|
res_op_nquerydomain,
|
||||||
|
|
||||||
|
res_op_last = res_op_nquerydomain,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char *
|
||||||
|
res_op_string (enum res_op op)
|
||||||
|
{
|
||||||
|
switch (op)
|
||||||
|
{
|
||||||
|
case res_op_search:
|
||||||
|
return "res_search";
|
||||||
|
case res_op_query:
|
||||||
|
return "res_query";
|
||||||
|
case res_op_querydomain:
|
||||||
|
return "res_querydomain";
|
||||||
|
case res_op_nsearch:
|
||||||
|
return "res_nsearch";
|
||||||
|
case res_op_nquery:
|
||||||
|
return "res_nquery";
|
||||||
|
case res_op_nquerydomain:
|
||||||
|
return "res_nquerydomain";
|
||||||
|
}
|
||||||
|
FAIL_EXIT1 ("invalid res_op value %d", (int) op);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Call libresolv function OP to look up PROBE_NAME, with an answer
|
||||||
|
buffer of SIZE bytes. Check that the advertised UDP buffer size is
|
||||||
|
in fact EXPECTED_BUFFER_SIZE. */
|
||||||
|
static void
|
||||||
|
do_res_search (const char *probe_name, enum res_op op, size_t size,
|
||||||
|
size_t expected_buffer_size)
|
||||||
|
{
|
||||||
|
if (test_verbose)
|
||||||
|
printf ("info: testing %s with buffer size %zu\n",
|
||||||
|
res_op_string (op), size);
|
||||||
|
unsigned char *buffer = xmalloc (size);
|
||||||
|
int ret = -1;
|
||||||
|
switch (op)
|
||||||
|
{
|
||||||
|
case res_op_search:
|
||||||
|
ret = res_search (probe_name, C_IN, T_A, buffer, size);
|
||||||
|
break;
|
||||||
|
case res_op_query:
|
||||||
|
ret = res_query (probe_name, C_IN, T_A, buffer, size);
|
||||||
|
break;
|
||||||
|
case res_op_nsearch:
|
||||||
|
ret = res_nsearch (&_res, probe_name, C_IN, T_A, buffer, size);
|
||||||
|
break;
|
||||||
|
case res_op_nquery:
|
||||||
|
ret = res_nquery (&_res, probe_name, C_IN, T_A, buffer, size);
|
||||||
|
break;
|
||||||
|
case res_op_querydomain:
|
||||||
|
case res_op_nquerydomain:
|
||||||
|
{
|
||||||
|
char *example_stripped = xstrdup (probe_name);
|
||||||
|
char *dot_example = strstr (example_stripped, ".example");
|
||||||
|
if (dot_example != NULL && strcmp (dot_example, ".example") == 0)
|
||||||
|
{
|
||||||
|
/* Truncate the domain name. */
|
||||||
|
*dot_example = '\0';
|
||||||
|
if (op == res_op_querydomain)
|
||||||
|
ret = res_querydomain
|
||||||
|
(example_stripped, "example", C_IN, T_A, buffer, size);
|
||||||
|
else
|
||||||
|
ret = res_nquerydomain
|
||||||
|
(&_res, example_stripped, "example", C_IN, T_A, buffer, size);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
FAIL_EXIT1 ("invalid probe name: %s", probe_name);
|
||||||
|
free (example_stripped);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
TEST_VERIFY_EXIT (ret > 12);
|
||||||
|
unsigned char *end = buffer + ret;
|
||||||
|
|
||||||
|
HEADER *hd = (HEADER *) buffer;
|
||||||
|
TEST_VERIFY (ntohs (hd->qdcount) == 1);
|
||||||
|
TEST_VERIFY (ntohs (hd->ancount) == 1);
|
||||||
|
/* Skip over the header. */
|
||||||
|
unsigned char *p = buffer + sizeof (*hd);
|
||||||
|
/* Skip over the question. */
|
||||||
|
ret = dn_skipname (p, end);
|
||||||
|
TEST_VERIFY_EXIT (ret > 0);
|
||||||
|
p += ret;
|
||||||
|
TEST_VERIFY_EXIT (end - p >= 4);
|
||||||
|
p += 4;
|
||||||
|
/* Skip over the RNAME and the RR header, but stop at the RDATA
|
||||||
|
length. */
|
||||||
|
ret = dn_skipname (p, end);
|
||||||
|
TEST_VERIFY_EXIT (ret > 0);
|
||||||
|
p += ret;
|
||||||
|
TEST_VERIFY_EXIT (end - p >= 2 + 2 + 4 + 2 + 4);
|
||||||
|
p += 2 + 2 + 4;
|
||||||
|
/* The IP address should be 4 bytes long. */
|
||||||
|
TEST_VERIFY_EXIT (p[0] == 0);
|
||||||
|
TEST_VERIFY_EXIT (p[1] == 4);
|
||||||
|
/* Extract the address information. */
|
||||||
|
p += 2;
|
||||||
|
struct response_data *data = decode_address (p, 4);
|
||||||
|
|
||||||
|
verify_response_data_payload (data, expected_buffer_size);
|
||||||
|
|
||||||
|
free (buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
run_test (const char *probe_name)
|
||||||
|
{
|
||||||
|
if (test_verbose)
|
||||||
|
printf ("\ninfo: * use_edns=%d use_dnssec=%d\n",
|
||||||
|
use_edns, use_dnssec);
|
||||||
|
check_hostent (gethostbyname (probe_name));
|
||||||
|
check_hostent (gethostbyname2 (probe_name, AF_INET));
|
||||||
|
check_hostent (gethostbyname2 (probe_name, AF_INET6));
|
||||||
|
do_ai (AF_UNSPEC);
|
||||||
|
do_ai (AF_INET);
|
||||||
|
do_ai (AF_INET6);
|
||||||
|
|
||||||
|
for (int op = 0; op <= res_op_last; ++op)
|
||||||
|
{
|
||||||
|
do_res_search (probe_name, op, 301, 512);
|
||||||
|
do_res_search (probe_name, op, 511, 512);
|
||||||
|
do_res_search (probe_name, op, 512, 512);
|
||||||
|
do_res_search (probe_name, op, 513, 513);
|
||||||
|
do_res_search (probe_name, op, 657, 657);
|
||||||
|
do_res_search (probe_name, op, 1199, 1199);
|
||||||
|
do_res_search (probe_name, op, 1200, 1200);
|
||||||
|
do_res_search (probe_name, op, 1201, 1200);
|
||||||
|
do_res_search (probe_name, op, 65535, 1200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
do_test (void)
|
||||||
|
{
|
||||||
|
for (int do_edns = 0; do_edns < 2; ++do_edns)
|
||||||
|
for (int do_dnssec = 0; do_dnssec < 2; ++do_dnssec)
|
||||||
|
for (int do_tcp = 0; do_tcp < 2; ++do_tcp)
|
||||||
|
{
|
||||||
|
struct resolv_test *aux = resolv_test_start
|
||||||
|
((struct resolv_redirect_config)
|
||||||
|
{
|
||||||
|
.response_callback = response,
|
||||||
|
});
|
||||||
|
|
||||||
|
use_edns = do_edns;
|
||||||
|
if (do_edns)
|
||||||
|
_res.options |= RES_USE_EDNS0;
|
||||||
|
use_dnssec = do_dnssec;
|
||||||
|
if (do_dnssec)
|
||||||
|
_res.options |= RES_USE_DNSSEC;
|
||||||
|
|
||||||
|
char *probe_name = xstrdup (EDNS_PROBE_EXAMPLE);
|
||||||
|
if (do_tcp)
|
||||||
|
{
|
||||||
|
char *n = xasprintf ("tcp.%s", probe_name);
|
||||||
|
free (probe_name);
|
||||||
|
probe_name = n;
|
||||||
|
}
|
||||||
|
|
||||||
|
run_test (probe_name);
|
||||||
|
|
||||||
|
free (probe_name);
|
||||||
|
resolv_test_end (aux);
|
||||||
|
}
|
||||||
|
|
||||||
|
free_response_data ();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#include <support/test-driver.c>
|
@ -429,6 +429,7 @@ struct query_info
|
|||||||
char qname[MAXDNAME];
|
char qname[MAXDNAME];
|
||||||
uint16_t qclass;
|
uint16_t qclass;
|
||||||
uint16_t qtype;
|
uint16_t qtype;
|
||||||
|
struct resolv_edns_info edns;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Update *INFO from the specified DNS packet. */
|
/* Update *INFO from the specified DNS packet. */
|
||||||
@ -436,10 +437,26 @@ static void
|
|||||||
parse_query (struct query_info *info,
|
parse_query (struct query_info *info,
|
||||||
const unsigned char *buffer, size_t length)
|
const unsigned char *buffer, size_t length)
|
||||||
{
|
{
|
||||||
if (length < 12)
|
HEADER hd;
|
||||||
|
_Static_assert (sizeof (hd) == 12, "DNS header size");
|
||||||
|
if (length < sizeof (hd))
|
||||||
FAIL_EXIT1 ("malformed DNS query: too short: %zu bytes", length);
|
FAIL_EXIT1 ("malformed DNS query: too short: %zu bytes", length);
|
||||||
|
memcpy (&hd, buffer, sizeof (hd));
|
||||||
|
|
||||||
int ret = dn_expand (buffer, buffer + length, buffer + 12,
|
if (ntohs (hd.qdcount) != 1)
|
||||||
|
FAIL_EXIT1 ("malformed DNS query: wrong question count: %d",
|
||||||
|
(int) ntohs (hd.qdcount));
|
||||||
|
if (ntohs (hd.ancount) != 0)
|
||||||
|
FAIL_EXIT1 ("malformed DNS query: wrong answer count: %d",
|
||||||
|
(int) ntohs (hd.ancount));
|
||||||
|
if (ntohs (hd.nscount) != 0)
|
||||||
|
FAIL_EXIT1 ("malformed DNS query: wrong authority count: %d",
|
||||||
|
(int) ntohs (hd.nscount));
|
||||||
|
if (ntohs (hd.arcount) > 1)
|
||||||
|
FAIL_EXIT1 ("malformed DNS query: wrong additional count: %d",
|
||||||
|
(int) ntohs (hd.arcount));
|
||||||
|
|
||||||
|
int ret = dn_expand (buffer, buffer + length, buffer + sizeof (hd),
|
||||||
info->qname, sizeof (info->qname));
|
info->qname, sizeof (info->qname));
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
FAIL_EXIT1 ("malformed DNS query: cannot uncompress QNAME");
|
FAIL_EXIT1 ("malformed DNS query: cannot uncompress QNAME");
|
||||||
@ -457,6 +474,37 @@ parse_query (struct query_info *info,
|
|||||||
memcpy (&qtype_qclass, buffer + 12 + ret, sizeof (qtype_qclass));
|
memcpy (&qtype_qclass, buffer + 12 + ret, sizeof (qtype_qclass));
|
||||||
info->qclass = ntohs (qtype_qclass.qclass);
|
info->qclass = ntohs (qtype_qclass.qclass);
|
||||||
info->qtype = ntohs (qtype_qclass.qtype);
|
info->qtype = ntohs (qtype_qclass.qtype);
|
||||||
|
|
||||||
|
memset (&info->edns, 0, sizeof (info->edns));
|
||||||
|
if (ntohs (hd.arcount) > 0)
|
||||||
|
{
|
||||||
|
/* Parse EDNS record. */
|
||||||
|
struct __attribute__ ((packed, aligned (1)))
|
||||||
|
{
|
||||||
|
uint8_t root;
|
||||||
|
uint16_t rtype;
|
||||||
|
uint16_t payload;
|
||||||
|
uint8_t edns_extended_rcode;
|
||||||
|
uint8_t edns_version;
|
||||||
|
uint16_t flags;
|
||||||
|
uint16_t rdatalen;
|
||||||
|
} rr;
|
||||||
|
_Static_assert (sizeof (rr) == 11, "EDNS record size");
|
||||||
|
|
||||||
|
if (remaining < 4 + sizeof (rr))
|
||||||
|
FAIL_EXIT1 ("mailformed DNS query: no room for EDNS record");
|
||||||
|
memcpy (&rr, buffer + 12 + ret + 4, sizeof (rr));
|
||||||
|
if (rr.root != 0)
|
||||||
|
FAIL_EXIT1 ("malformed DNS query: invalid OPT RNAME: %d\n", rr.root);
|
||||||
|
if (rr.rtype != htons (41))
|
||||||
|
FAIL_EXIT1 ("malformed DNS query: invalid OPT type: %d\n",
|
||||||
|
ntohs (rr.rtype));
|
||||||
|
info->edns.active = true;
|
||||||
|
info->edns.extended_rcode = rr.edns_extended_rcode;
|
||||||
|
info->edns.version = rr.edns_version;
|
||||||
|
info->edns.flags = ntohs (rr.flags);
|
||||||
|
info->edns.payload_size = ntohs (rr.payload);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -586,6 +634,7 @@ server_thread_udp_process_one (struct resolv_test *obj, int server_index)
|
|||||||
.query_length = length,
|
.query_length = length,
|
||||||
.server_index = server_index,
|
.server_index = server_index,
|
||||||
.tcp = false,
|
.tcp = false,
|
||||||
|
.edns = qinfo.edns,
|
||||||
};
|
};
|
||||||
struct resolv_response_builder *b = response_builder_allocate (query, length);
|
struct resolv_response_builder *b = response_builder_allocate (query, length);
|
||||||
obj->config.response_callback
|
obj->config.response_callback
|
||||||
@ -821,6 +870,7 @@ server_thread_tcp_client (void *arg)
|
|||||||
.query_length = query_length,
|
.query_length = query_length,
|
||||||
.server_index = closure->server_index,
|
.server_index = closure->server_index,
|
||||||
.tcp = true,
|
.tcp = true,
|
||||||
|
.edns = qinfo.edns,
|
||||||
};
|
};
|
||||||
struct resolv_response_builder *b = response_builder_allocate
|
struct resolv_response_builder *b = response_builder_allocate
|
||||||
(query_buffer, query_length);
|
(query_buffer, query_length);
|
||||||
|
@ -25,6 +25,16 @@
|
|||||||
|
|
||||||
__BEGIN_DECLS
|
__BEGIN_DECLS
|
||||||
|
|
||||||
|
/* Information about EDNS properties of a DNS query. */
|
||||||
|
struct resolv_edns_info
|
||||||
|
{
|
||||||
|
bool active;
|
||||||
|
uint8_t extended_rcode;
|
||||||
|
uint8_t version;
|
||||||
|
uint16_t flags;
|
||||||
|
uint16_t payload_size;
|
||||||
|
};
|
||||||
|
|
||||||
/* This struct provides context information when the response callback
|
/* This struct provides context information when the response callback
|
||||||
specified in struct resolv_redirect_config is invoked. */
|
specified in struct resolv_redirect_config is invoked. */
|
||||||
struct resolv_response_context
|
struct resolv_response_context
|
||||||
@ -33,6 +43,7 @@ struct resolv_response_context
|
|||||||
size_t query_length;
|
size_t query_length;
|
||||||
int server_index;
|
int server_index;
|
||||||
bool tcp;
|
bool tcp;
|
||||||
|
struct resolv_edns_info edns;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* This opaque struct is used to construct responses from within the
|
/* This opaque struct is used to construct responses from within the
|
||||||
|
Reference in New Issue
Block a user