mirror of
https://sourceware.org/git/glibc.git
synced 2024-12-23 03:10:05 +00:00
9fe3c80c7c
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.
1203 lines
36 KiB
C
1203 lines
36 KiB
C
/* 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>
|
|
#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;
|
|
}
|
|
}
|
|
|
|
/* 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,
|
|
};
|
|
|
|
resolv_test_init ();
|
|
|
|
/* Create all the servers, to reserve the necessary ports. */
|
|
for (int server_index = 0; server_index < config.nscount; ++server_index)
|
|
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);
|
|
|
|
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)
|
|
{
|
|
_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);
|
|
}
|