glibc/support/resolv_test.c

1236 lines
37 KiB
C
Raw Normal View History

/* DNS test framework and libresolv redirection.
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 <support/resolv_test.h>
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <nss.h>
#include <resolv.h>
#include <search.h>
#include <stdlib.h>
#include <string.h>
#include <support/check.h>
#include <support/namespace.h>
#include <support/support.h>
#include <support/test-driver.h>
#include <support/xsocket.h>
#include <support/xthread.h>
#include <support/xunistd.h>
Fix sys/socket.h namespace issues from sys/uio.h inclusion (bug 21426). sys/socket.h includes sys/uio.h to get the definition of the iovec structure. POSIX allows sys/socket.h to make all sys/uio.h symbols visible. However, all of sys/uio.h is XSI-shaded, so for non-XSI POSIX this results in conformtest failures (for sys/socket.h and other headers that include it): Namespace violation: "UIO_MAXIOV" Namespace violation: "readv" Namespace violation: "writev" Now, there is some ambiguity in POSIX about what namespace reservations apply in this case - see http://austingroupbugs.net/view.php?id=1127 - but glibc convention would still avoid declaring readv and writev, for example, for feature test macros that don't include them (if only headers from the relevant standard are included), even if such declarations are permitted, so there is a bug here according to glibc conventions. This patch moves the struct iovec definition to a new bits/types/struct_iovec.h header and includes that from sys/socket.h instead of including the whole of sys/uio.h. This fixes the namespace issue; however, three files in glibc that were relying on the implicit inclusion needed to be updated to include sys/uio.h explicitly. So there is a question of whether sys/socket.h should continue to include sys/uio.h under some conditions, such as __USE_XOPEN or __USE_MISC or __USE_XOPEN || __USE_MISC, for greater compatibility with code that (wrongly) expects this optional inclusion to be present there. (I think the three affected files in glibc should still have explicit sys/uio.h inclusions added in any case, however.) Tested for x86_64. [BZ #21426] * misc/bits/types/struct_iovec.h: New file. * misc/Makefile (headers): Add bits/types/struct_iovec.h. * include/bits/types/struct_iovec.h: New file. * bits/uio.h (struct iovec): Replace by inclusion of <bits/types/struct_iovec.h>. * sysdeps/unix/sysv/linux/bits/uio.h (struct iovec): Likewise. * socket/sys/socket.h: Include <bits/types/struct_iovec.h> instead of <sys/uio.h>. * nptl/tst-cancel4.c: Include <sys/uio.h> * posix/test-errno.c: Likewise. * support/resolv_test.c: Likewise. * conform/Makefile (test-xfail-POSIX2008/arpa/inet.h/conform): Remove. (test-xfail-POSIX2008/netdb.h/conform): Likewise. (test-xfail-POSIX2008/netinet/in.h/conform): Likewise. (test-xfail-POSIX2008/sys/socket.h/conform): Likewise.
2017-04-25 19:52:47 +02:00
#include <sys/uio.h>
#include <unistd.h>
/* Response builder. */
enum
{
max_response_length = 65536
};
/* List of pointers to be freed. The hash table implementation
(struct hsearch_data) does not provide a way to deallocate all
objects, so this approach is used to avoid memory leaks. */
struct to_be_freed
{
struct to_be_freed *next;
void *ptr;
};
struct resolv_response_builder
{
const unsigned char *query_buffer;
size_t query_length;
size_t offset; /* Bytes written so far in buffer. */
ns_sect section; /* Current section in the DNS packet. */
unsigned int truncate_bytes; /* Bytes to remove at end of response. */
bool drop; /* Discard generated response. */
bool close; /* Close TCP client connection. */
/* Offset of the two-byte RDATA length field in the currently
written RDATA sub-structure. 0 if no RDATA is being written. */
size_t current_rdata_offset;
/* Hash table for locating targets for label compression. */
struct hsearch_data compression_offsets;
/* List of pointers which need to be freed. Used for domain names
involved in label compression. */
struct to_be_freed *to_be_freed;
/* Must be last. Not zeroed for performance reasons. */
unsigned char buffer[max_response_length];
};
/* Response builder. */
/* Add a pointer to the list of pointers to be freed when B is
deallocated. */
static void
response_push_pointer_to_free (struct resolv_response_builder *b, void *ptr)
{
if (ptr == NULL)
return;
struct to_be_freed *e = xmalloc (sizeof (*e));
*e = (struct to_be_freed) {b->to_be_freed, ptr};
b->to_be_freed = e;
}
void
resolv_response_init (struct resolv_response_builder *b,
struct resolv_response_flags flags)
{
if (b->offset > 0)
FAIL_EXIT1 ("response_init: called at offset %zu", b->offset);
if (b->query_length < 12)
FAIL_EXIT1 ("response_init called for a query of size %zu",
b->query_length);
if (flags.rcode > 15)
FAIL_EXIT1 ("response_init: invalid RCODE %u", flags.rcode);
/* Copy the transaction ID. */
b->buffer[0] = b->query_buffer[0];
b->buffer[1] = b->query_buffer[1];
/* Initialize the flags. */
b->buffer[2] = 0x80; /* Mark as response. */
b->buffer[2] |= b->query_buffer[2] & 0x01; /* Copy the RD bit. */
if (flags.tc)
b->buffer[2] |= 0x02;
b->buffer[3] = 0x80 | flags.rcode; /* Always set RA. */
/* Fill in the initial section count values. */
b->buffer[4] = flags.qdcount >> 8;
b->buffer[5] = flags.qdcount;
b->buffer[6] = flags.ancount >> 8;
b->buffer[7] = flags.ancount;
b->buffer[8] = flags.nscount >> 8;
b->buffer[9] = flags.nscount;
b->buffer[10] = flags.adcount >> 8;
b->buffer[11] = flags.adcount;
b->offset = 12;
}
void
resolv_response_section (struct resolv_response_builder *b, ns_sect section)
{
if (b->offset == 0)
FAIL_EXIT1 ("resolv_response_section: response_init not called before");
if (section < b->section)
FAIL_EXIT1 ("resolv_response_section: cannot go back to previous section");
b->section = section;
}
/* Add a single byte to B. */
static inline void
response_add_byte (struct resolv_response_builder *b, unsigned char ch)
{
if (b->offset == max_response_length)
FAIL_EXIT1 ("DNS response exceeds 64 KiB limit");
b->buffer[b->offset] = ch;
++b->offset;
}
/* Add a 16-bit word VAL to B, in big-endian format. */
static void
response_add_16 (struct resolv_response_builder *b, uint16_t val)
{
response_add_byte (b, val >> 8);
response_add_byte (b, val);
}
/* Increment the pers-section record counter in the packet header. */
static void
response_count_increment (struct resolv_response_builder *b)
{
unsigned int offset = b->section;
offset = 4 + 2 * offset;
++b->buffer[offset + 1];
if (b->buffer[offset + 1] == 0)
{
/* Carry. */
++b->buffer[offset];
if (b->buffer[offset] == 0)
/* Overflow. */
FAIL_EXIT1 ("too many records in section");
}
}
void
resolv_response_add_question (struct resolv_response_builder *b,
const char *name, uint16_t class, uint16_t type)
{
if (b->offset == 0)
FAIL_EXIT1 ("resolv_response_add_question: "
"resolv_response_init not called");
if (b->section != ns_s_qd)
FAIL_EXIT1 ("resolv_response_add_question: "
"must be called in the question section");
resolv_response_add_name (b, name);
response_add_16 (b, type);
response_add_16 (b, class);
response_count_increment (b);
}
void
resolv_response_add_name (struct resolv_response_builder *b,
const char *const origname)
{
/* Normalized name. */
char *name;
/* Normalized name with case preserved. */
char *name_case;
{
size_t namelen = strlen (origname);
/* Remove trailing dots. FIXME: Handle trailing quoted dots. */
while (namelen > 0 && origname[namelen - 1] == '.')
--namelen;
name = xmalloc (namelen + 1);
name_case = xmalloc (namelen + 1);
/* Copy and convert to lowercase. FIXME: This needs to normalize
escaping as well. */
for (size_t i = 0; i < namelen; ++i)
{
char ch = origname[i];
name_case[i] = ch;
if ('A' <= ch && ch <= 'Z')
ch = ch - 'A' + 'a';
name[i] = ch;
}
name[namelen] = 0;
name_case[namelen] = 0;
}
char *name_start = name;
char *name_case_start = name_case;
bool compression = false;
while (*name)
{
/* Search for a previous name we can reference. */
ENTRY new_entry =
{
.key = name,
.data = (void *) (uintptr_t) b->offset,
};
/* If the label can be a compression target because it is at a
reachable offset, add it to the hash table. */
ACTION action;
if (b->offset < (1 << 12))
action = ENTER;
else
action = FIND;
/* Search for known compression offsets in the hash table. */
ENTRY *e;
if (hsearch_r (new_entry, action, &e, &b->compression_offsets) == 0)
{
if (action == FIND && errno == ESRCH)
/* Fall through. */
e = NULL;
else
FAIL_EXIT1 ("hsearch_r failure in name compression: %m");
}
/* The name is known. Reference the previous location. */
if (e != NULL && e->data != new_entry.data)
{
size_t old_offset = (uintptr_t) e->data;
response_add_byte (b, 0xC0 | (old_offset >> 8));
response_add_byte (b, old_offset);
compression = true;
break;
}
/* The name does not exist yet. Write one label. First, add
room for the label length. */
size_t buffer_label_offset = b->offset;
response_add_byte (b, 0);
/* Copy the label. */
while (true)
{
char ch = *name_case;
if (ch == '\0')
break;
++name;
++name_case;
if (ch == '.')
break;
/* FIXME: Handle escaping. */
response_add_byte (b, ch);
}
/* Patch in the label length. */
size_t label_length = b->offset - buffer_label_offset - 1;
if (label_length == 0)
FAIL_EXIT1 ("empty label in name compression: %s", origname);
if (label_length > 63)
FAIL_EXIT1 ("label too long in name compression: %s", origname);
b->buffer[buffer_label_offset] = label_length;
/* Continue with the tail of the name and the next label. */
}
if (compression)
{
/* If we found an immediate match for the name, we have not put
it into the hash table, and can free it immediately. */
if (name == name_start)
free (name_start);
else
response_push_pointer_to_free (b, name_start);
}
else
{
/* Terminate the sequence of labels. With compression, this is
implicit in the compression reference. */
response_add_byte (b, 0);
response_push_pointer_to_free (b, name_start);
}
free (name_case_start);
}
void
resolv_response_open_record (struct resolv_response_builder *b,
const char *name,
uint16_t class, uint16_t type, uint32_t ttl)
{
if (b->section == ns_s_qd)
FAIL_EXIT1 ("resolv_response_open_record called in question section");
if (b->current_rdata_offset != 0)
FAIL_EXIT1 ("resolv_response_open_record called with open record");
resolv_response_add_name (b, name);
response_add_16 (b, type);
response_add_16 (b, class);
response_add_16 (b, ttl >> 16);
response_add_16 (b, ttl);
b->current_rdata_offset = b->offset;
/* Add room for the RDATA length. */
response_add_16 (b, 0);
}
void
resolv_response_close_record (struct resolv_response_builder *b)
{
size_t rdata_offset = b->current_rdata_offset;
if (rdata_offset == 0)
FAIL_EXIT1 ("response_close_record called without open record");
size_t rdata_length = b->offset - rdata_offset - 2;
if (rdata_length > 65535)
FAIL_EXIT1 ("RDATA length %zu exceeds limit", rdata_length);
b->buffer[rdata_offset] = rdata_length >> 8;
b->buffer[rdata_offset + 1] = rdata_length;
response_count_increment (b);
b->current_rdata_offset = 0;
}
void
resolv_response_add_data (struct resolv_response_builder *b,
const void *data, size_t length)
{
size_t remaining = max_response_length - b->offset;
if (remaining < length)
FAIL_EXIT1 ("resolv_response_add_data: not enough room for %zu bytes",
length);
memcpy (b->buffer + b->offset, data, length);
b->offset += length;
}
void
resolv_response_drop (struct resolv_response_builder *b)
{
b->drop = true;
}
void
resolv_response_close (struct resolv_response_builder *b)
{
b->close = true;
}
void
resolv_response_truncate_data (struct resolv_response_builder *b, size_t count)
{
if (count > 65535)
FAIL_EXIT1 ("resolv_response_truncate_data: argument too large: %zu",
count);
b->truncate_bytes = count;
}
size_t
resolv_response_length (const struct resolv_response_builder *b)
{
return b->offset;
}
unsigned char *
resolv_response_buffer (const struct resolv_response_builder *b)
{
unsigned char *result = xmalloc (b->offset);
memcpy (result, b->buffer, b->offset);
return result;
}
static struct resolv_response_builder *
response_builder_allocate
(const unsigned char *query_buffer, size_t query_length)
{
struct resolv_response_builder *b = xmalloc (sizeof (*b));
memset (b, 0, offsetof (struct resolv_response_builder, buffer));
b->query_buffer = query_buffer;
b->query_length = query_length;
TEST_VERIFY_EXIT (hcreate_r (10000, &b->compression_offsets) != 0);
return b;
}
static void
response_builder_free (struct resolv_response_builder *b)
{
struct to_be_freed *current = b->to_be_freed;
while (current != NULL)
{
struct to_be_freed *next = current->next;
free (current->ptr);
free (current);
current = next;
}
hdestroy_r (&b->compression_offsets);
free (b);
}
/* DNS query processing. */
/* Data extracted from the question section of a DNS packet. */
struct query_info
{
char qname[MAXDNAME];
uint16_t qclass;
uint16_t qtype;
struct resolv_edns_info edns;
};
/* Update *INFO from the specified DNS packet. */
static void
parse_query (struct query_info *info,
const unsigned char *buffer, size_t length)
{
HEADER hd;
_Static_assert (sizeof (hd) == 12, "DNS header size");
if (length < sizeof (hd))
FAIL_EXIT1 ("malformed DNS query: too short: %zu bytes", length);
memcpy (&hd, buffer, sizeof (hd));
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));
if (ret < 0)
FAIL_EXIT1 ("malformed DNS query: cannot uncompress QNAME");
/* Obtain QTYPE and QCLASS. */
size_t remaining = length - (12 + ret);
struct
{
uint16_t qtype;
uint16_t qclass;
} qtype_qclass;
if (remaining < sizeof (qtype_qclass))
FAIL_EXIT1 ("malformed DNS query: "
"query lacks QCLASS/QTYPE, QNAME: %s", info->qname);
memcpy (&qtype_qclass, buffer + 12 + ret, sizeof (qtype_qclass));
info->qclass = ntohs (qtype_qclass.qclass);
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);
}
}
/* Main testing framework. */
/* Per-server information. One struct is allocated for each test
server. */
struct resolv_test_server
{
/* Local address of the server. UDP and TCP use the same port. */
struct sockaddr_in address;
/* File descriptor of the UDP server, or -1 if this server is
disabled. */
int socket_udp;
/* File descriptor of the TCP server, or -1 if this server is
disabled. */
int socket_tcp;
/* Counter of the number of responses processed so far. */
size_t response_number;
/* Thread handles for the server threads (if not disabled in the
configuration). */
pthread_t thread_udp;
pthread_t thread_tcp;
};
/* Main struct for keeping track of libresolv redirection and
testing. */
struct resolv_test
{
/* After initialization, any access to the struct must be performed
while this lock is acquired. */
pthread_mutex_t lock;
/* Data for each test server. */
struct resolv_test_server servers[resolv_max_test_servers];
/* Used if config.single_thread_udp is true. */
pthread_t thread_udp_single;
struct resolv_redirect_config config;
bool termination_requested;
};
/* Function implementing a server thread. */
typedef void (*thread_callback) (struct resolv_test *, int server_index);
/* Storage for thread-specific data, for passing to the
thread_callback function. */
struct thread_closure
{
struct resolv_test *obj; /* Current test object. */
thread_callback callback; /* Function to call. */
int server_index; /* Index of the implemented server. */
};
/* Wrap response_callback as a function which can be passed to
pthread_create. */
static void *
thread_callback_wrapper (void *arg)
{
struct thread_closure *closure = arg;
closure->callback (closure->obj, closure->server_index);
free (closure);
return NULL;
}
/* Start a server thread for the specified SERVER_INDEX, implemented
by CALLBACK. */
static pthread_t
start_server_thread (struct resolv_test *obj, int server_index,
thread_callback callback)
{
struct thread_closure *closure = xmalloc (sizeof (*closure));
*closure = (struct thread_closure)
{
.obj = obj,
.callback = callback,
.server_index = server_index,
};
return xpthread_create (NULL, thread_callback_wrapper, closure);
}
/* Process one UDP query. Return false if a termination requested has
been detected. */
static bool
server_thread_udp_process_one (struct resolv_test *obj, int server_index)
{
unsigned char query[512];
struct sockaddr_storage peer;
socklen_t peerlen = sizeof (peer);
size_t length = xrecvfrom (obj->servers[server_index].socket_udp,
query, sizeof (query), 0,
(struct sockaddr *) &peer, &peerlen);
/* Check for termination. */
{
bool termination_requested;
xpthread_mutex_lock (&obj->lock);
termination_requested = obj->termination_requested;
xpthread_mutex_unlock (&obj->lock);
if (termination_requested)
return false;
}
struct query_info qinfo;
parse_query (&qinfo, query, length);
if (test_verbose > 0)
{
if (test_verbose > 1)
printf ("info: UDP server %d: incoming query:"
" %zd bytes, %s/%u/%u, tnxid=0x%02x%02x\n",
server_index, length, qinfo.qname, qinfo.qclass, qinfo.qtype,
query[0], query[1]);
else
printf ("info: UDP server %d: incoming query:"
" %zd bytes, %s/%u/%u\n",
server_index, length, qinfo.qname, qinfo.qclass, qinfo.qtype);
}
struct resolv_response_context ctx =
{
.query_buffer = query,
.query_length = length,
.server_index = server_index,
.tcp = false,
.edns = qinfo.edns,
};
struct resolv_response_builder *b = response_builder_allocate (query, length);
obj->config.response_callback
(&ctx, b, qinfo.qname, qinfo.qclass, qinfo.qtype);
if (b->drop)
{
if (test_verbose)
printf ("info: UDP server %d: dropping response to %s/%u/%u\n",
server_index, qinfo.qname, qinfo.qclass, qinfo.qtype);
}
else
{
if (test_verbose)
{
if (b->offset >= 12)
printf ("info: UDP server %d: sending response:"
" %zu bytes, RCODE %d (for %s/%u/%u)\n",
server_index, b->offset, b->buffer[3] & 0x0f,
qinfo.qname, qinfo.qclass, qinfo.qtype);
else
printf ("info: UDP server %d: sending response: %zu bytes"
" (for %s/%u/%u)\n",
server_index, b->offset,
qinfo.qname, qinfo.qclass, qinfo.qtype);
if (b->truncate_bytes > 0)
printf ("info: truncated by %u bytes\n", b->truncate_bytes);
}
size_t to_send = b->offset;
if (to_send < b->truncate_bytes)
to_send = 0;
else
to_send -= b->truncate_bytes;
/* Ignore most errors here because the other end may have closed
the socket. */
if (sendto (obj->servers[server_index].socket_udp,
b->buffer, to_send, 0,
(struct sockaddr *) &peer, peerlen) < 0)
TEST_VERIFY_EXIT (errno != EBADF);
}
response_builder_free (b);
return true;
}
/* UDP thread_callback function. Variant for one thread per
server. */
static void
server_thread_udp (struct resolv_test *obj, int server_index)
{
while (server_thread_udp_process_one (obj, server_index))
;
}
/* Single-threaded UDP processing function, for the single_thread_udp
case. */
static void *
server_thread_udp_single (void *closure)
{
struct resolv_test *obj = closure;
struct pollfd fds[resolv_max_test_servers];
for (int server_index = 0; server_index < resolv_max_test_servers;
++server_index)
if (obj->config.servers[server_index].disable_udp)
fds[server_index] = (struct pollfd) {.fd = -1};
else
{
fds[server_index] = (struct pollfd)
{
.fd = obj->servers[server_index].socket_udp,
.events = POLLIN
};
/* Make the socket non-blocking. */
int flags = fcntl (obj->servers[server_index].socket_udp, F_GETFL, 0);
if (flags < 0)
FAIL_EXIT1 ("fcntl (F_GETFL): %m");
flags |= O_NONBLOCK;
if (fcntl (obj->servers[server_index].socket_udp, F_SETFL, flags) < 0)
FAIL_EXIT1 ("fcntl (F_SETFL): %m");
}
while (true)
{
xpoll (fds, resolv_max_test_servers, -1);
for (int server_index = 0; server_index < resolv_max_test_servers;
++server_index)
if (fds[server_index].revents != 0)
{
if (!server_thread_udp_process_one (obj, server_index))
goto out;
fds[server_index].revents = 0;
}
}
out:
return NULL;
}
/* Start the single UDP handler thread (for the single_thread_udp
case). */
static void
start_server_thread_udp_single (struct resolv_test *obj)
{
obj->thread_udp_single
= xpthread_create (NULL, server_thread_udp_single, obj);
}
/* Data describing a TCP client connect. */
struct tcp_thread_closure
{
struct resolv_test *obj;
int server_index;
int client_socket;
};
/* Read a complete DNS query packet. If EOF_OK, an immediate
end-of-file condition is acceptable. */
static bool
read_fully (int fd, void *buf, size_t len, bool eof_ok)
{
const void *const end = buf + len;
while (buf < end)
{
ssize_t ret = read (fd, buf, end - buf);
if (ret == 0)
{
if (!eof_ok)
{
support_record_failure ();
printf ("error: unexpected EOF on TCP connection\n");
}
return false;
}
else if (ret < 0)
{
if (!eof_ok || errno != ECONNRESET)
{
support_record_failure ();
printf ("error: TCP read: %m\n");
}
return false;
}
buf += ret;
eof_ok = false;
}
return true;
}
/* Write an array of iovecs. Terminate the process on failure. */
static void
writev_fully (int fd, struct iovec *buffers, size_t count)
{
while (count > 0)
{
/* Skip zero-length write requests. */
if (buffers->iov_len == 0)
{
++buffers;
--count;
continue;
}
/* Try to rewrite the remaing buffers. */
ssize_t ret = writev (fd, buffers, count);
if (ret < 0)
FAIL_EXIT1 ("writev: %m");
if (ret == 0)
FAIL_EXIT1 ("writev: invalid return value zero");
/* Find the buffers that were successfully written. */
while (ret > 0)
{
if (count == 0)
FAIL_EXIT1 ("internal writev consistency failure");
/* Current buffer was partially written. */
if (buffers->iov_len > (size_t) ret)
{
buffers->iov_base += ret;
buffers->iov_len -= ret;
ret = 0;
}
else
{
ret -= buffers->iov_len;
buffers->iov_len = 0;
++buffers;
--count;
}
}
}
}
/* Thread callback for handling a single established TCP connection to
a client. */
static void *
server_thread_tcp_client (void *arg)
{
struct tcp_thread_closure *closure = arg;
while (true)
{
/* Read packet length. */
uint16_t query_length;
if (!read_fully (closure->client_socket,
&query_length, sizeof (query_length), true))
break;
query_length = ntohs (query_length);
/* Read the packet. */
unsigned char *query_buffer = xmalloc (query_length);
read_fully (closure->client_socket, query_buffer, query_length, false);
struct query_info qinfo;
parse_query (&qinfo, query_buffer, query_length);
if (test_verbose > 0)
{
if (test_verbose > 1)
printf ("info: UDP server %d: incoming query:"
" %d bytes, %s/%u/%u, tnxid=0x%02x%02x\n",
closure->server_index, query_length,
qinfo.qname, qinfo.qclass, qinfo.qtype,
query_buffer[0], query_buffer[1]);
else
printf ("info: TCP server %d: incoming query:"
" %u bytes, %s/%u/%u\n",
closure->server_index, query_length,
qinfo.qname, qinfo.qclass, qinfo.qtype);
}
struct resolv_response_context ctx =
{
.query_buffer = query_buffer,
.query_length = query_length,
.server_index = closure->server_index,
.tcp = true,
.edns = qinfo.edns,
};
struct resolv_response_builder *b = response_builder_allocate
(query_buffer, query_length);
closure->obj->config.response_callback
(&ctx, b, qinfo.qname, qinfo.qclass, qinfo.qtype);
if (b->drop)
{
if (test_verbose)
printf ("info: TCP server %d: dropping response to %s/%u/%u\n",
closure->server_index,
qinfo.qname, qinfo.qclass, qinfo.qtype);
}
else
{
if (test_verbose)
printf ("info: TCP server %d: sending response: %zu bytes"
" (for %s/%u/%u)\n",
closure->server_index, b->offset,
qinfo.qname, qinfo.qclass, qinfo.qtype);
uint16_t length = htons (b->offset);
size_t to_send = b->offset;
if (to_send < b->truncate_bytes)
to_send = 0;
else
to_send -= b->truncate_bytes;
struct iovec buffers[2] =
{
{&length, sizeof (length)},
{b->buffer, to_send}
};
writev_fully (closure->client_socket, buffers, 2);
}
bool close_flag = b->close;
response_builder_free (b);
free (query_buffer);
if (close_flag)
break;
}
xclose (closure->client_socket);
free (closure);
return NULL;
}
/* thread_callback for the TCP case. Accept connections and create a
new thread for each client. */
static void
server_thread_tcp (struct resolv_test *obj, int server_index)
{
while (true)
{
/* Get the client conenction. */
int client_socket = xaccept
(obj->servers[server_index].socket_tcp, NULL, NULL);
/* Check for termination. */
xpthread_mutex_lock (&obj->lock);
if (obj->termination_requested)
{
xpthread_mutex_unlock (&obj->lock);
xclose (client_socket);
break;
}
xpthread_mutex_unlock (&obj->lock);
/* Spawn a new thread for handling this connection. */
struct tcp_thread_closure *closure = xmalloc (sizeof (*closure));
*closure = (struct tcp_thread_closure)
{
.obj = obj,
.server_index = server_index,
.client_socket = client_socket,
};
pthread_t thr
= xpthread_create (NULL, server_thread_tcp_client, closure);
/* TODO: We should keep track of this thread so that we can
block in resolv_test_end until it has exited. */
xpthread_detach (thr);
}
}
/* Create UDP and TCP server sockets. */
static void
make_server_sockets (struct resolv_test_server *server)
{
while (true)
{
server->socket_udp = xsocket (AF_INET, SOCK_DGRAM, IPPROTO_UDP);
server->socket_tcp = xsocket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
/* Pick the address for the UDP socket. */
server->address = (struct sockaddr_in)
{
.sin_family = AF_INET,
.sin_addr = {.s_addr = htonl (INADDR_LOOPBACK)}
};
xbind (server->socket_udp,
(struct sockaddr *)&server->address, sizeof (server->address));
/* Retrieve the address. */
socklen_t addrlen = sizeof (server->address);
xgetsockname (server->socket_udp,
(struct sockaddr *)&server->address, &addrlen);
/* Bind the TCP socket to the same address. */
{
int on = 1;
xsetsockopt (server->socket_tcp, SOL_SOCKET, SO_REUSEADDR,
&on, sizeof (on));
}
if (bind (server->socket_tcp,
(struct sockaddr *)&server->address,
sizeof (server->address)) != 0)
{
/* Port collision. The UDP bind succeeded, but the TCP BIND
failed. We assume here that the kernel will pick the
next local UDP address randomly. */
if (errno == EADDRINUSE)
{
xclose (server->socket_udp);
xclose (server->socket_tcp);
continue;
}
FAIL_EXIT1 ("TCP bind: %m");
}
xlisten (server->socket_tcp, 5);
break;
}
}
/* Like make_server_sockets, but the caller supplies the address to
use. */
static void
make_server_sockets_for_address (struct resolv_test_server *server,
const struct sockaddr *addr)
{
server->socket_udp = xsocket (AF_INET, SOCK_DGRAM, IPPROTO_UDP);
server->socket_tcp = xsocket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (addr->sa_family == AF_INET)
server->address = *(const struct sockaddr_in *) addr;
else
/* We cannot store the server address in the socket. This should
not matter if disable_redirect is used. */
server->address = (struct sockaddr_in) { .sin_family = 0, };
xbind (server->socket_udp,
(struct sockaddr *)&server->address, sizeof (server->address));
xbind (server->socket_tcp,
(struct sockaddr *)&server->address, sizeof (server->address));
xlisten (server->socket_tcp, 5);
}
/* One-time initialization of NSS. */
static void
resolv_redirect_once (void)
{
/* Only use nss_dns. */
__nss_configure_lookup ("hosts", "dns");
__nss_configure_lookup ("networks", "dns");
/* Enter a network namespace for isolation and firewall state
cleanup. The tests will still work if these steps fail, but they
may be less reliable. */
support_become_root ();
support_enter_network_namespace ();
}
pthread_once_t resolv_redirect_once_var = PTHREAD_ONCE_INIT;
void
resolv_test_init (void)
{
/* Perform one-time initialization of NSS. */
xpthread_once (&resolv_redirect_once_var, resolv_redirect_once);
}
/* Copy the search path from CONFIG.search to the _res object. */
static void
set_search_path (struct resolv_redirect_config config)
{
memset (_res.defdname, 0, sizeof (_res.defdname));
memset (_res.dnsrch, 0, sizeof (_res.dnsrch));
char *current = _res.defdname;
char *end = current + sizeof (_res.defdname);
for (unsigned int i = 0;
i < sizeof (config.search) / sizeof (config.search[0]); ++i)
{
if (config.search[i] == NULL)
continue;
size_t length = strlen (config.search[i]) + 1;
size_t remaining = end - current;
TEST_VERIFY_EXIT (length <= remaining);
memcpy (current, config.search[i], length);
_res.dnsrch[i] = current;
current += length;
}
}
struct resolv_test *
resolv_test_start (struct resolv_redirect_config config)
{
/* Apply configuration defaults. */
if (config.nscount == 0)
config.nscount = resolv_max_test_servers;
struct resolv_test *obj = xmalloc (sizeof (*obj));
*obj = (struct resolv_test) {
.config = config,
.lock = PTHREAD_MUTEX_INITIALIZER,
};
if (!config.disable_redirect)
resolv_test_init ();
/* Create all the servers, to reserve the necessary ports. */
for (int server_index = 0; server_index < config.nscount; ++server_index)
if (config.disable_redirect && config.server_address_overrides != NULL)
make_server_sockets_for_address
(obj->servers + server_index,
config.server_address_overrides[server_index]);
else
make_server_sockets (obj->servers + server_index);
/* Start server threads. Disable the server ports, as
requested. */
for (int server_index = 0; server_index < config.nscount; ++server_index)
{
struct resolv_test_server *server = obj->servers + server_index;
if (config.servers[server_index].disable_udp)
{
xclose (server->socket_udp);
server->socket_udp = -1;
}
else if (!config.single_thread_udp)
server->thread_udp = start_server_thread (obj, server_index,
server_thread_udp);
if (config.servers[server_index].disable_tcp)
{
xclose (server->socket_tcp);
server->socket_tcp = -1;
}
else
server->thread_tcp = start_server_thread (obj, server_index,
server_thread_tcp);
}
if (config.single_thread_udp)
start_server_thread_udp_single (obj);
if (config.disable_redirect)
return obj;
int timeout = 1;
/* Initialize libresolv. */
TEST_VERIFY_EXIT (res_init () == 0);
/* Disable IPv6 name server addresses. The code below only
overrides the IPv4 addresses. */
__res_iclose (&_res, true);
_res._u._ext.nscount = 0;
/* Redirect queries to the server socket. */
if (test_verbose)
{
printf ("info: old timeout value: %d\n", _res.retrans);
printf ("info: old retry attempt value: %d\n", _res.retry);
printf ("info: old _res.options: 0x%lx\n", _res.options);
printf ("info: old _res.nscount value: %d\n", _res.nscount);
printf ("info: old _res.ndots value: %d\n", _res.ndots);
}
_res.retrans = timeout;
_res.retry = 4;
_res.nscount = config.nscount;
_res.options = RES_INIT | RES_RECURSE | RES_DEFNAMES | RES_DNSRCH;
_res.ndots = 1;
if (test_verbose)
{
printf ("info: new timeout value: %d\n", _res.retrans);
printf ("info: new retry attempt value: %d\n", _res.retry);
printf ("info: new _res.options: 0x%lx\n", _res.options);
printf ("info: new _res.nscount value: %d\n", _res.nscount);
printf ("info: new _res.ndots value: %d\n", _res.ndots);
}
for (int server_index = 0; server_index < config.nscount; ++server_index)
{
TEST_VERIFY_EXIT (obj->servers[server_index].address.sin_port != 0);
_res.nsaddr_list[server_index] = obj->servers[server_index].address;
if (test_verbose)
{
char buf[256];
TEST_VERIFY_EXIT
(inet_ntop (AF_INET, &obj->servers[server_index].address.sin_addr,
buf, sizeof (buf)) != NULL);
printf ("info: server %d: %s/%u\n",
server_index, buf,
htons (obj->servers[server_index].address.sin_port));
}
}
set_search_path (config);
return obj;
}
void
resolv_test_end (struct resolv_test *obj)
{
res_close ();
xpthread_mutex_lock (&obj->lock);
obj->termination_requested = true;
xpthread_mutex_unlock (&obj->lock);
/* Send trigger packets to unblock the server threads. */
for (int server_index = 0; server_index < obj->config.nscount;
++server_index)
{
if (!obj->config.servers[server_index].disable_udp)
{
int sock = xsocket (AF_INET, SOCK_DGRAM, IPPROTO_UDP);
xsendto (sock, "", 1, 0,
(struct sockaddr *) &obj->servers[server_index].address,
sizeof (obj->servers[server_index].address));
xclose (sock);
}
if (!obj->config.servers[server_index].disable_tcp)
{
int sock = xsocket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
xconnect (sock,
(struct sockaddr *) &obj->servers[server_index].address,
sizeof (obj->servers[server_index].address));
xclose (sock);
}
}
if (obj->config.single_thread_udp)
xpthread_join (obj->thread_udp_single);
/* Wait for the server threads to terminate. */
for (int server_index = 0; server_index < obj->config.nscount;
++server_index)
{
if (!obj->config.servers[server_index].disable_udp)
{
if (!obj->config.single_thread_udp)
xpthread_join (obj->servers[server_index].thread_udp);
xclose (obj->servers[server_index].socket_udp);
}
if (!obj->config.servers[server_index].disable_tcp)
{
xpthread_join (obj->servers[server_index].thread_tcp);
xclose (obj->servers[server_index].socket_tcp);
}
}
free (obj);
}