From patchwork Sat Dec 31 13:15:43 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Florian Weimer X-Patchwork-Id: 709922 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from sourceware.org (server1.sourceware.org [209.132.180.131]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 3trP475rXZz9t1P for ; Sun, 1 Jan 2017 00:16:23 +1100 (AEDT) Authentication-Results: ozlabs.org; dkim=pass (1024-bit key; secure) header.d=sourceware.org header.i=@sourceware.org header.b="MxT67lqp"; dkim-atps=neutral DomainKey-Signature: a=rsa-sha1; c=nofws; d=sourceware.org; h=list-id :list-unsubscribe:list-subscribe:list-archive:list-post :list-help:sender:to:from:subject:message-id:date:mime-version :content-type; q=dns; s=default; b=SBY7XYAakuWc9B9gE3/14t7Xc9tVt jLtd9y5zS5GTryxZ37yvTO2BxnJ8YDcOdBwJOpjYjbFu/5OBh6rGmCdYZsRryoOl Jl/O/o/Dm/G2vLMV1RkCJpMerAsD9sXzLCSljlIcTiYVf3xF3a6Uxbj1QmS2kTjs D5IgJgh7ZOaw40= DKIM-Signature: v=1; a=rsa-sha1; c=relaxed; d=sourceware.org; h=list-id :list-unsubscribe:list-subscribe:list-archive:list-post :list-help:sender:to:from:subject:message-id:date:mime-version :content-type; s=default; bh=14wHVKAvaUxmjTmEq9bCmNG7jHY=; b=MxT 67lqpHpa6Pi8w6v5qa8HCJLgu4tuLY8h0Y2N8IsbcpyOjtAXFDLOlE6rXsQtJxcZ rQh27iRwf2JJFQkgWH6iyeAlNH+mMOlALo/EKBjp1xbWF9ONPOK2AJBguwIcCKW9 4OnBHoXqcP+dGU+rk9AXie0H5SaXGKlYXsnigVcE= Received: (qmail 39175 invoked by alias); 31 Dec 2016 13:16:06 -0000 Mailing-List: contact libc-alpha-help@sourceware.org; run by ezmlm Precedence: bulk List-Id: List-Unsubscribe: List-Subscribe: List-Archive: List-Post: List-Help: , Sender: libc-alpha-owner@sourceware.org Delivered-To: mailing list libc-alpha@sourceware.org Received: (qmail 39158 invoked by uid 89); 31 Dec 2016 13:16:04 -0000 Authentication-Results: sourceware.org; auth=none X-Virus-Found: No X-Spam-SWARE-Status: No, score=0.1 required=5.0 tests=BAYES_50, KAM_LAZY_DOMAIN_SECURITY, KAM_MXURI, RP_MATCHES_RCVD, SPF_HELO_PASS autolearn=ham version=3.3.2 spammy=Instructions, 1215, Format, 1202 X-HELO: mx1.redhat.com To: GNU C Library From: Florian Weimer Subject: [PATCH] libresolv tests Message-ID: <45f7bb28-1a3a-ce4c-a8b9-7e9a4707d1fe@redhat.com> Date: Sat, 31 Dec 2016 14:15:43 +0100 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 Thunderbird/45.5.1 MIME-Version: 1.0 The attached patch depends on the support/ enhancements I posted earlier. It contains some of the short-running tests from my out-of-tree resolver test suite. The included changes to support/ are fairly substantial, but I expect that other tests will use these facilities over time. I plan to contribute more such libresolv tests, but some of them have to be xtests because they have many timeouts and really long run times. Further work (RES_USE_INET6 deprecation and various libresolv cleanups and fixes) is blocked on the availability of the libresolv test framework within the glibc source tree. Thanks, Florian resolv: Add beginnings of a libresolv test suite 2016-12-31 Florian Weimer * resolv/Makefile (tests): Add tst-bug18665, tst-bug18665-tcp, tst-res_use_inet6, tst-resolv-basic, tst-resolv-network, tst-resolv-search. (tst-bug18665, tst-bug18665-tcp, tst-res_use_inet6) (tst-resolv-basic, tst-resolv-network, tst-resolv-search): Link with libresolv and libpthread. * resolv/tst-bug18665.c: New file. * resolv/tst-bug18665-tcp: Likewise. * resolv/tst-res_use_inet6: Likewise. * resolv/tst-resolv-basic: Likewise. * resolv/tst-resolv-network: Likewise. * resolv/tst-resolv-search: Likewise. * support/Makefile (libsupport-routines): Add check_addrinfo, check_dns_packet, check_hostent, check_netent, resolv_test, support_format_address_family, support_format_addrinfo, support_format_dns_packet, support_format_herrno, support_format_hostent, support_format_netent, support_run_diff, xaccept, xbind, xconnect, xfclose, xfopen, xgetsockname, xlisten, xmemstream, xpoll, xpthread_once, xrecvfrom, xsendto, xsetsockopt, xstrdup, xwrite. * support/check_addrinfo.c: New file. * support/check_dns_packet.c: Likewise. * support/check_hostent.c: Likewise. * support/check_netent.c: Likewise. * support/check_nss.h: Likewise. * support/format_nss.h: Likewise. * support/resolv_test.c: Likewise. * support/resolv_test.h: Likewise. * support/run_diff.h: Likewise. * support/support_format_address_family.c: Likewise. * support/support_format_addrinfo.c: Likewise. * support/support_format_dns_packet.c: Likewise. * support/support_format_herrno.c: Likewise. * support/support_format_hostent.c: Likewise. * support/support_format_netent.c: Likewise. * support/support_run_diff.c: Likewise. * support/xaccept.c: Likewise. * support/xbind.c: Likewise. * support/xconnect.c: Likewise. * support/xfclose.c: Likewise. * support/xfopen.c: Likewise. * support/xgetsockname.c: Likewise. * support/xlisten.c: Likewise. * support/xmemstream.c: Likewise. * support/xmemstream.h: Likewise. * support/xpoll.c: Likewise. * support/xpthread_once.c: Likewise. * support/xrecvfrom.c: Likewise. * support/xsendto.c: Likewise. * support/xsetsockopt.c: Likewise. * support/xstdio.h: Likewise. * support/xstrdup.c: Likewise. * support/support.h (xstrdup): Declare. * support/xsocket.h (xsetsockopt, xgetsockname, xconnect, xbind) (xlisten, xaccept, xsendto, xrecvfrom, xpoll): Likewise. * support/xthread.h (xpthread_once): Likwise. * support/xunistd.h (xwrite): Declare. diff --git a/resolv/Makefile b/resolv/Makefile index bd086e0..5eb10e3 100644 --- a/resolv/Makefile +++ b/resolv/Makefile @@ -39,7 +39,16 @@ extra-libs := libresolv libnss_dns ifeq ($(have-thread-library),yes) extra-libs += libanl routines += gai_sigqueue -tests += tst-res_hconf_reorder + +tests += \ + tst-bug18665 \ + tst-bug18665-tcp \ + tst-res_hconf_reorder \ + tst-res_use_inet6 \ + tst-resolv-basic \ + tst-resolv-network \ + tst-resolv-search \ + endif extra-libs-others = $(extra-libs) libresolv-routines := res_comp res_debug \ @@ -108,3 +117,10 @@ tst-leaks2-ENV = MALLOC_TRACE=$(objpfx)tst-leaks2.mtrace $(objpfx)mtrace-tst-leaks2.out: $(objpfx)tst-leaks2.out $(common-objpfx)malloc/mtrace $(objpfx)tst-leaks2.mtrace > $@; \ $(evaluate-test) + +$(objpfx)tst-bug18665-tcp: $(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-resolv-basic: $(objpfx)libresolv.so $(shared-thread-library) +$(objpfx)tst-resolv-network: $(objpfx)libresolv.so $(shared-thread-library) +$(objpfx)tst-resolv-search: $(objpfx)libresolv.so $(shared-thread-library) diff --git a/resolv/tst-bug18665-tcp.c b/resolv/tst-bug18665-tcp.c new file mode 100644 index 0000000..fcd3baf --- /dev/null +++ b/resolv/tst-bug18665-tcp.c @@ -0,0 +1,229 @@ +/* Test __libc_res_nsend buffer mismanagement, basic TCP coverage. + Copyright (C) 2016 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 + . */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; + +static int initial_address_count = 1; +static int subsequent_address_count = 2000; +static int response_number = 0; + +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); + + /* If not using TCP, just force its use. */ + if (!ctx->tcp) + { + struct resolv_response_flags flags = {.tc = true}; + resolv_response_init (b, flags); + resolv_response_add_question (b, qname, qclass, qtype); + return; + } + + struct resolv_response_flags flags = {}; + resolv_response_init (b, flags); + resolv_response_add_question (b, qname, qclass, qtype); + + resolv_response_section (b, ns_s_an); + + /* The number of addresses (in the additional section) for the name + server record (in the authoritative section). */ + int address_count; + xpthread_mutex_lock (&lock); + ++response_number; + if (response_number == 1) + address_count = initial_address_count; + else if (response_number == 2) + { + address_count = 0; + resolv_response_drop (b); + resolv_response_close (b); + } + else + address_count = subsequent_address_count; + xpthread_mutex_unlock (&lock); + + /* Only add the address record to the answer section if we requested + any name server addresses. */ + if (address_count > 0) + { + resolv_response_open_record (b, qname, qclass, qtype, 0); + switch (qtype) + { + case T_A: + { + char ipv4[4] = {10, response_number >> 8, response_number, 0}; + ipv4[3] = 2 * ctx->tcp + 4 * ctx->server_index; + resolv_response_add_data (b, &ipv4, sizeof (ipv4)); + } + break; + case T_AAAA: + { + char ipv6[16] + = {0x20, 0x01, 0xd, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, + response_number >> 8, response_number, 0, 0}; + ipv6[15] = 2 * ctx->tcp + 4 * ctx->server_index; + resolv_response_add_data (b, &ipv6, sizeof (ipv6)); + } + break; + default: + support_record_failure (); + printf ("error: unexpected QTYPE: %s/%u/%u\n", + qname, qclass, qtype); + } + resolv_response_close_record (b); + + /* Add the name server record. */ + resolv_response_section (b, ns_s_ns); + resolv_response_open_record (b, "example", C_IN, T_NS, 0); + resolv_response_add_name (b, "ns.example"); + resolv_response_close_record (b); + + /* Increase the response size with name server addresses. These + addresses are not copied out of nss_dns, and thus do not + trigger getaddrinfo retries with a larger buffer, making + testing more predictable. */ + resolv_response_section (b, ns_s_ar); + for (int i = 1; i <= address_count; ++i) + { + resolv_response_open_record (b, "ns.example", qclass, qtype, 0); + switch (qtype) + { + case T_A: + { + char ipv4[4] = {response_number, i >> 8, i, 0}; + ipv4[3] = 2 * ctx->tcp + 4 * ctx->server_index; + resolv_response_add_data (b, &ipv4, sizeof (ipv4)); + } + break; + case T_AAAA: + { + char ipv6[16] + = {0x20, 0x01, 0xd, 0xb8, 0, 0, 0, 0, 0, 0, + response_number >> 8, response_number, + i >> 8, i, 0, 0}; + ipv6[15] = 2 * ctx->tcp + 4 * ctx->server_index; + resolv_response_add_data (b, &ipv6, sizeof (ipv6)); + } + break; + default: + support_record_failure (); + printf ("error: unexpected QTYPE: %s/%u/%u\n", + qname, qclass, qtype); + } + resolv_response_close_record (b); + } + } +} + +static char * +expected_result (unsigned port, unsigned response_number) +{ + struct xmemstream mem; + xopen_memstream (&mem); + /* We fail the second TCP query to the first server by closing the + connection immediately, without returning any data. This should + cause failover to the second server. */ + int server_index = 1; + fprintf (mem.out, "address: STREAM/TCP 10.%u.%u.%u %u\n", + (response_number >> 8) & 0xff, response_number & 0xff, + 2 + 4 * server_index, port); + fprintf (mem.out, "address: STREAM/TCP 2001:db8::%x:%x %u\n", + (response_number + 1) & 0xffff, + 2 + 4 * server_index, port); + xfclose_memstream (&mem); + return mem.buffer; +} + +static void +test_different_sizes (void) +{ + struct addrinfo hints = + { + .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_STREAM, + .ai_protocol = IPPROTO_TCP, + }; + struct addrinfo *ai; + char *expected; + int ret; + + /* This magic number produces a response size close to 2048 + bytes. */ + initial_address_count = 124; + response_number = 0; + + ret = getaddrinfo ("www.example", "80", &hints, &ai); + expected = expected_result (80, 3); + check_addrinfo ("www.example:80", ai, ret, expected); + if (ret == 0) + freeaddrinfo (ai); + free (expected); + + response_number = 0; + ret = getaddrinfo ("www123.example", "80", &hints, &ai); + if (ret == 0) + freeaddrinfo (ai); + + response_number = 0; + ret = getaddrinfo ("www1234.example", "80", &hints, &ai); + if (ret == 0) + freeaddrinfo (ai); + + response_number = 0; + ret = getaddrinfo ("www12345.example", "80", &hints, &ai); + if (ret == 0) + freeaddrinfo (ai); +} + +static int +do_test (void) +{ + struct resolv_test *obj = resolv_test_start + ((struct resolv_redirect_config) + { + .response_callback = response + }); + + test_different_sizes (); + + _res.options |= RES_SNGLKUP; + test_different_sizes (); + + _res.options |= RES_SNGLKUPREOP; + test_different_sizes (); + + resolv_test_end (obj); + return 0; +} + +#include diff --git a/resolv/tst-bug18665.c b/resolv/tst-bug18665.c new file mode 100644 index 0000000..2d0cdb2 --- /dev/null +++ b/resolv/tst-bug18665.c @@ -0,0 +1,138 @@ +/* Test for __libc_res_nsend buffer mismanagent (bug 18665), UDP case. + Copyright (C) 2016 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 + . */ + +#include +#include +#include +#include +#include +#include +#include +#include + +static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; + +static int initial_address_count; +static int response_count; + +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); + struct resolv_response_flags flags = {}; + resolv_response_init (b, flags); + resolv_response_add_question (b, qname, qclass, qtype); + + resolv_response_section (b, ns_s_an); + + /* Add many A/AAAA records to the second response. */ + int address_count; + xpthread_mutex_lock (&lock); + if (response_count == 0) + address_count = initial_address_count; + else + address_count = 2000; + ++response_count; + xpthread_mutex_unlock (&lock); + + for (int i = 0; i < address_count; ++i) + { + resolv_response_open_record (b, qname, qclass, qtype, 0); + switch (qtype) + { + case T_A: + { + char ipv4[4] = {10, i >> 8, i, 0}; + ipv4[3] = 2 * ctx->tcp + 4 * ctx->server_index; + resolv_response_add_data (b, &ipv4, sizeof (ipv4)); + } + break; + case T_AAAA: + { + char ipv6[16] + = {0x20, 0x01, 0xd, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, + i >> 8, i, 0}; + ipv6[15] = 2 * ctx->tcp + 4 * ctx->server_index; + resolv_response_add_data (b, &ipv6, sizeof (ipv6)); + } + break; + default: + support_record_failure (); + printf ("error: unexpected QTYPE: %s/%u/%u\n", + qname, qclass, qtype); + } + resolv_response_close_record (b); + } +} + +static void +test_different_sizes (void) +{ + struct addrinfo hints = { .ai_family = AF_UNSPEC, }; + struct addrinfo *ai; + int ret; + + /* This magic number produces a response size close to 2048 + bytes. */ + initial_address_count = 126; + response_count = 0; + + ret = getaddrinfo ("www.example", "80", &hints, &ai); + if (ret == 0) + freeaddrinfo (ai); + + response_count = 0; + ret = getaddrinfo ("www123.example", "80", &hints, &ai); + if (ret == 0) + freeaddrinfo (ai); + + response_count = 0; + ret = getaddrinfo ("www1234.example", "80", &hints, &ai); + if (ret == 0) + freeaddrinfo (ai); + + response_count = 0; + ret = getaddrinfo ("www12345.example", "80", &hints, &ai); + if (ret == 0) + freeaddrinfo (ai); +} + +static int +do_test (void) +{ + struct resolv_test *obj = resolv_test_start + ((struct resolv_redirect_config) + { + .response_callback = response + }); + + test_different_sizes (); + + _res.options |= RES_SNGLKUP; + test_different_sizes (); + + _res.options |= RES_SNGLKUPREOP; + test_different_sizes (); + + resolv_test_end (obj); + return 0; +} + +#include diff --git a/resolv/tst-res_use_inet6.c b/resolv/tst-res_use_inet6.c new file mode 100644 index 0000000..5ed0ad0 --- /dev/null +++ b/resolv/tst-res_use_inet6.c @@ -0,0 +1,201 @@ +/* Basic functionality tests for inet6 option processing. + Copyright (C) 2016 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 + . */ + +#include +#include +#include +#include +#include +#include + +static void +response (const struct resolv_response_context *ctx, + struct resolv_response_builder *b, + const char *qname, uint16_t qclass, uint16_t qtype) +{ + bool include_both = strcmp (qname, "both.example") == 0; + bool include_a = qtype == T_A || include_both; + bool include_aaaa = qtype == T_AAAA || include_both; + + resolv_response_init (b, (struct resolv_response_flags) {}); + resolv_response_add_question (b, qname, qclass, qtype); + resolv_response_section (b, ns_s_an); + if (include_a) + { + char ipv4[4] = {192, 0, 2, 17}; + resolv_response_open_record (b, qname, qclass, T_A, 0); + resolv_response_add_data (b, &ipv4, sizeof (ipv4)); + resolv_response_close_record (b); + } + if (include_aaaa) + { + char ipv6[16] + = {0x20, 0x01, 0xd, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}; + resolv_response_open_record (b, qname, qclass, T_AAAA, 0); + resolv_response_add_data (b, &ipv6, sizeof (ipv6)); + resolv_response_close_record (b); + } +} + +/* Test that getaddrinfo is not influenced by RES_USE_INET6. */ +static void +test_gai (void) +{ + { + struct addrinfo hints = + { + .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_STREAM, + .ai_protocol = IPPROTO_TCP, + }; + struct addrinfo *ai; + int ret = getaddrinfo ("www1.example", "80", &hints, &ai); + check_addrinfo ("getaddrinfo AF_UNSPEC www1.example", ai, ret, + "address: STREAM/TCP 192.0.2.17 80\n" + "address: STREAM/TCP 2001:db8::1 80\n"); + if (ret == 0) + freeaddrinfo (ai); + ret = getaddrinfo ("both.example", "80", &hints, &ai); + /* Combined A/AAAA responses currently result in address + duplication. */ + check_addrinfo ("getaddrinfo AF_UNSPEC both.example", ai, ret, + "address: STREAM/TCP 192.0.2.17 80\n" + "address: STREAM/TCP 192.0.2.17 80\n" + "address: STREAM/TCP 2001:db8::1 80\n" + "address: STREAM/TCP 2001:db8::1 80\n"); + if (ret == 0) + freeaddrinfo (ai); + } + { + struct addrinfo hints = + { + .ai_family = AF_INET, + .ai_socktype = SOCK_STREAM, + .ai_protocol = IPPROTO_TCP, + }; + struct addrinfo *ai; + int ret = getaddrinfo ("www1.example", "80", &hints, &ai); + check_addrinfo ("getaddrinfo AF_INET www1.example", ai, ret, + "address: STREAM/TCP 192.0.2.17 80\n"); + if (ret == 0) + freeaddrinfo (ai); + ret = getaddrinfo ("both.example", "80", &hints, &ai); + check_addrinfo ("getaddrinfo AF_INET both.example", ai, ret, + "address: STREAM/TCP 192.0.2.17 80\n"); + if (ret == 0) + freeaddrinfo (ai); + } + { + struct addrinfo hints = + { + .ai_family = AF_INET6, + .ai_socktype = SOCK_STREAM, + .ai_protocol = IPPROTO_TCP, + }; + struct addrinfo *ai; + int ret = getaddrinfo ("www1.example", "80", &hints, &ai); + check_addrinfo ("getaddrinfo (AF_INET6)", ai, ret, + "address: STREAM/TCP 2001:db8::1 80\n"); + if (ret == 0) + freeaddrinfo (ai); + ret = getaddrinfo ("both.example", "80", &hints, &ai); + check_addrinfo ("getaddrinfo AF_INET6 both.example", ai, ret, + "address: STREAM/TCP 2001:db8::1 80\n"); + if (ret == 0) + freeaddrinfo (ai); + } +} + +/* Test that gethostbyname2 is not influenced by RES_USE_INET6. */ +static void +test_get2 (void) +{ + check_hostent ("gethostbyname2 AF_INET www1.example", + gethostbyname2 ("www1.example", AF_INET), + "name: www1.example\n" + "address: 192.0.2.17\n"); + check_hostent ("gethostbyname2 AF_INET both.example", + gethostbyname2 ("both.example", AF_INET), + "name: both.example\n" + "address: 192.0.2.17\n"); + + check_hostent ("gethostbyname2 AF_INET6 www1.example", + gethostbyname2 ("www1.example", AF_INET6), + "name: www1.example\n" + "address: 2001:db8::1\n"); + check_hostent ("gethostbyname2 AF_INET6 both.example", + gethostbyname2 ("both.example", AF_INET6), + "name: both.example\n" + "address: 2001:db8::1\n"); +} + +static void * +threadfunc (void *ignored) +{ + struct resolv_test *obj = resolv_test_start + ((struct resolv_redirect_config) + { + .response_callback = response + }); + + check_hostent ("gethostbyname (\"www1.example\")", + gethostbyname ("www1.example"), + "name: www1.example\n" + "address: 192.0.2.17\n"); + check_hostent ("gethostbyname (\"both.example\")", + gethostbyname ("both.example"), + "name: both.example\n" + "address: 192.0.2.17\n"); + test_get2 (); + test_gai (); + + _res.options |= RES_USE_INET6; + check_hostent ("gethostbyname (\"www1.example\")", + gethostbyname ("www1.example"), + "name: www1.example\n" + "address: 2001:db8::1\n"); + check_hostent ("gethostbyname (\"both.example\")", + gethostbyname ("both.example"), + "name: both.example\n" + "address: 2001:db8::1\n"); + test_get2 (); + test_gai (); + + resolv_test_end (obj); + + return NULL; +} + +static int +do_test (void) +{ + resolv_test_init (); + + /* Attempt to run on a non-main thread first. */ + { + pthread_t thr = xpthread_create (NULL, threadfunc, NULL); + xpthread_join (thr); + } + + /* Try the main thread next. */ + threadfunc (NULL); + + return 0; +} + +#include diff --git a/resolv/tst-resolv-basic.c b/resolv/tst-resolv-basic.c new file mode 100644 index 0000000..78c581d --- /dev/null +++ b/resolv/tst-resolv-basic.c @@ -0,0 +1,315 @@ +/* Test basic nss_dns functionality and the resolver test harness itself. + Copyright (C) 2016 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 + . */ + +#include +#include +#include +#include +#include +#include +#include +#include + +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 "t." prefix can be used to request TCP fallback. */ + bool force_tcp; + if (strncmp ("t.", qname, 2) == 0) + force_tcp = true; + else + force_tcp = false; + const char *qname_compare; + if (force_tcp) + qname_compare = qname + 2; + else + qname_compare = qname; + enum {www, alias, nxdomain} requested_qname; + if (strcmp (qname_compare, "www.example") == 0) + requested_qname = www; + else if (strcmp (qname_compare, "alias.example") == 0) + requested_qname = alias; + else if (strcmp (qname_compare, "nxdomain.example") == 0) + requested_qname = nxdomain; + 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}; + if (requested_qname == nxdomain) + flags.rcode = 3; /* NXDOMAIN */ + resolv_response_init (b, flags); + resolv_response_add_question (b, qname, qclass, qtype); + if (requested_qname == nxdomain || flags.tc) + return; + + resolv_response_section (b, ns_s_an); + switch (requested_qname) + { + case www: + resolv_response_open_record (b, qname, qclass, qtype, 0); + break; + case alias: + resolv_response_open_record (b, qname, qclass, T_CNAME, 0); + resolv_response_add_name (b, "www.example"); + resolv_response_close_record (b); + resolv_response_open_record (b, "www.example", qclass, qtype, 0); + break; + case nxdomain: + FAIL_EXIT1 ("unreachable"); + } + switch (qtype) + { + case T_A: + { + char ipv4[4] = {192, 0, 2, 17}; + ipv4[3] += requested_qname + 2 * ctx->tcp + 4 * ctx->server_index; + resolv_response_add_data (b, &ipv4, sizeof (ipv4)); + } + break; + case T_AAAA: + { + char ipv6[16] + = {0x20, 0x01, 0xd, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}; + ipv6[15] += requested_qname + 2 * ctx->tcp + 4 * ctx->server_index; + resolv_response_add_data (b, &ipv6, sizeof (ipv6)); + } + break; + default: + support_record_failure (); + printf ("error: unexpected QTYPE: %s/%u/%u\n", + qname, qclass, qtype); + } + resolv_response_close_record (b); +} + +static void +check_h (const char *name, int family, const char *expected) +{ + if (family == AF_INET) + { + char *query = xasprintf ("gethostbyname (\"%s\")", name); + check_hostent (query, gethostbyname (name), expected); + free (query); + } + { + char *query = xasprintf ("gethostbyname2 (\"%s\", %d)", name, family); + check_hostent (query, gethostbyname2 (name, family), expected); + free (query); + } + + bool too_small = true; + for (unsigned int offset = 0; offset < 8; ++offset) + for (unsigned int size = 1; too_small; ++size) + { + char *buf = xmalloc (offset + size); + too_small = false; + + struct hostent hostbuf; + struct hostent *result; + int herror; + if (family == AF_INET) + { + char *query = xasprintf ("gethostbyname (\"%s\") %u/%u", + name, offset, size); + int ret = gethostbyname_r + (name, &hostbuf, buf + offset, size, &result, &herror); + if (ret == 0) + { + h_errno = herror; + check_hostent (query, result, expected); + } + else if (ret == ERANGE) + too_small = true; + else + { + errno = ret; + FAIL_EXIT1 ("gethostbyname_r: %m"); + } + free (query); + memset (buf, 0, offset + size); + } + char *query = xasprintf ("gethostbyname2 (\"%s\", %d) %u/%u", + name, family, offset, size); + int ret = gethostbyname2_r + (name, family, &hostbuf, buf + offset, size, &result, &herror); + if (ret == 0) + { + h_errno = herror; + check_hostent (query, result, expected); + } + else if (ret == ERANGE) + too_small = true; + else + { + errno = ret; + FAIL_EXIT1 ("gethostbyname_r: %m"); + } + free (buf); + free (query); + } +} + +static void +check_ai (const char *name, const char *service, + int family, const char *expected) +{ + struct addrinfo hints = {.ai_family = family}; + struct addrinfo *ai; + char *query = xasprintf ("%s:%s [%d]", name, service, family); + int ret = getaddrinfo (name, service, &hints, &ai); + check_addrinfo (query, ai, ret, expected); + if (ret == 0) + freeaddrinfo (ai); + free (query); +} + +static int +do_test (void) +{ + struct resolv_test *aux = resolv_test_start + ((struct resolv_redirect_config) + { + .response_callback = response, + }); + + check_h ("www.example", AF_INET, + "name: www.example\n" + "address: 192.0.2.17\n"); + check_h ("alias.example", AF_INET, + "name: www.example\n" + "alias: alias.example\n" + "address: 192.0.2.18\n"); + check_h ("www.example", AF_INET6, + "name: www.example\n" + "address: 2001:db8::1\n"); + check_h ("alias.example", AF_INET6, + "name: www.example\n" + "alias: alias.example\n" + "address: 2001:db8::2\n"); + check_ai ("www.example", "80", AF_UNSPEC, + "address: STREAM/TCP 192.0.2.17 80\n" + "address: DGRAM/UDP 192.0.2.17 80\n" + "address: RAW/IP 192.0.2.17 80\n" + "address: STREAM/TCP 2001:db8::1 80\n" + "address: DGRAM/UDP 2001:db8::1 80\n" + "address: RAW/IP 2001:db8::1 80\n"); + check_ai ("alias.example", "80", AF_UNSPEC, + "address: STREAM/TCP 192.0.2.18 80\n" + "address: DGRAM/UDP 192.0.2.18 80\n" + "address: RAW/IP 192.0.2.18 80\n" + "address: STREAM/TCP 2001:db8::2 80\n" + "address: DGRAM/UDP 2001:db8::2 80\n" + "address: RAW/IP 2001:db8::2 80\n"); + check_ai ("www.example", "80", AF_INET, + "address: STREAM/TCP 192.0.2.17 80\n" + "address: DGRAM/UDP 192.0.2.17 80\n" + "address: RAW/IP 192.0.2.17 80\n"); + check_ai ("alias.example", "80", AF_INET, + "address: STREAM/TCP 192.0.2.18 80\n" + "address: DGRAM/UDP 192.0.2.18 80\n" + "address: RAW/IP 192.0.2.18 80\n"); + check_ai ("www.example", "80", AF_INET6, + "address: STREAM/TCP 2001:db8::1 80\n" + "address: DGRAM/UDP 2001:db8::1 80\n" + "address: RAW/IP 2001:db8::1 80\n"); + check_ai ("alias.example", "80", AF_INET6, + "address: STREAM/TCP 2001:db8::2 80\n" + "address: DGRAM/UDP 2001:db8::2 80\n" + "address: RAW/IP 2001:db8::2 80\n"); + + check_h ("t.www.example", AF_INET, + "name: t.www.example\n" + "address: 192.0.2.19\n"); + check_h ("t.alias.example", AF_INET, + "name: www.example\n" + "alias: t.alias.example\n" + "address: 192.0.2.20\n"); + check_h ("t.www.example", AF_INET6, + "name: t.www.example\n" + "address: 2001:db8::3\n"); + check_h ("t.alias.example", AF_INET6, + "name: www.example\n" + "alias: t.alias.example\n" + "address: 2001:db8::4\n"); + check_ai ("t.www.example", "80", AF_UNSPEC, + "address: STREAM/TCP 192.0.2.19 80\n" + "address: DGRAM/UDP 192.0.2.19 80\n" + "address: RAW/IP 192.0.2.19 80\n" + "address: STREAM/TCP 2001:db8::3 80\n" + "address: DGRAM/UDP 2001:db8::3 80\n" + "address: RAW/IP 2001:db8::3 80\n"); + check_ai ("t.alias.example", "80", AF_UNSPEC, + "address: STREAM/TCP 192.0.2.20 80\n" + "address: DGRAM/UDP 192.0.2.20 80\n" + "address: RAW/IP 192.0.2.20 80\n" + "address: STREAM/TCP 2001:db8::4 80\n" + "address: DGRAM/UDP 2001:db8::4 80\n" + "address: RAW/IP 2001:db8::4 80\n"); + check_ai ("t.www.example", "80", AF_INET, + "address: STREAM/TCP 192.0.2.19 80\n" + "address: DGRAM/UDP 192.0.2.19 80\n" + "address: RAW/IP 192.0.2.19 80\n"); + check_ai ("t.alias.example", "80", AF_INET, + "address: STREAM/TCP 192.0.2.20 80\n" + "address: DGRAM/UDP 192.0.2.20 80\n" + "address: RAW/IP 192.0.2.20 80\n"); + check_ai ("t.www.example", "80", AF_INET6, + "address: STREAM/TCP 2001:db8::3 80\n" + "address: DGRAM/UDP 2001:db8::3 80\n" + "address: RAW/IP 2001:db8::3 80\n"); + check_ai ("t.alias.example", "80", AF_INET6, + "address: STREAM/TCP 2001:db8::4 80\n" + "address: DGRAM/UDP 2001:db8::4 80\n" + "address: RAW/IP 2001:db8::4 80\n"); + + check_h ("nxdomain.example", AF_INET, + "error: HOST_NOT_FOUND\n"); + check_h ("nxdomain.example", AF_INET6, + "error: HOST_NOT_FOUND\n"); + check_ai ("nxdomain.example", "80", AF_UNSPEC, + "error: Name or service not known\n"); + check_ai ("nxdomain.example", "80", AF_INET, + "error: Name or service not known\n"); + check_ai ("nxdomain.example", "80", AF_INET6, + "error: Name or service not known\n"); + + check_h ("t.nxdomain.example", AF_INET, + "error: HOST_NOT_FOUND\n"); + check_h ("t.nxdomain.example", AF_INET6, + "error: HOST_NOT_FOUND\n"); + check_ai ("t.nxdomain.example", "80", AF_UNSPEC, + "error: Name or service not known\n"); + check_ai ("t.nxdomain.example", "80", AF_INET, + "error: Name or service not known\n"); + check_ai ("t.nxdomain.example", "80", AF_INET6, + "error: Name or service not known\n"); + + resolv_test_end (aux); + + return 0; +} + +#include diff --git a/resolv/tst-resolv-network.c b/resolv/tst-resolv-network.c new file mode 100644 index 0000000..f248527 --- /dev/null +++ b/resolv/tst-resolv-network.c @@ -0,0 +1,299 @@ +/* Test getnetbyname and getnetbyaddr. + Copyright (C) 2016 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 + . */ + +#include +#include +#include +#include +#include +#include +#include +#include + +static void +send_ptr (struct resolv_response_builder *b, + const char *qname, uint16_t qclass, uint16_t qtype, + const char *alias) +{ + resolv_response_init (b, (struct resolv_response_flags) {}); + resolv_response_add_question (b, qname, qclass, qtype); + resolv_response_section (b, ns_s_an); + resolv_response_open_record (b, qname, qclass, T_PTR, 0); + resolv_response_add_name (b, alias); + resolv_response_close_record (b); +} + +static void +handle_code (const struct resolv_response_context *ctx, + struct resolv_response_builder *b, + const char *qname, uint16_t qclass, uint16_t qtype, + int code) +{ + switch (code) + { + case 1: + send_ptr (b, qname, qclass, qtype, "1.in-addr.arpa"); + break; + case 2: + send_ptr (b, qname, qclass, qtype, "2.1.in-addr.arpa"); + break; + case 3: + send_ptr (b, qname, qclass, qtype, "3.2.1.in-addr.arpa"); + break; + case 4: + send_ptr (b, qname, qclass, qtype, "4.3.2.1.in-addr.arpa"); + break; + case 5: + /* Test multiple PTR records. */ + resolv_response_init (b, (struct resolv_response_flags) {}); + resolv_response_add_question (b, qname, qclass, qtype); + resolv_response_section (b, ns_s_an); + resolv_response_open_record (b, qname, qclass, T_PTR, 0); + resolv_response_add_name (b, "127.in-addr.arpa"); + resolv_response_close_record (b); + resolv_response_open_record (b, qname, qclass, T_PTR, 0); + resolv_response_add_name (b, "0.in-addr.arpa"); + resolv_response_close_record (b); + break; + case 6: + /* Test skipping of RRSIG record. */ + resolv_response_init (b, (struct resolv_response_flags) { }); + resolv_response_add_question (b, qname, qclass, qtype); + resolv_response_section (b, ns_s_an); + + resolv_response_open_record (b, qname, qclass, T_PTR, 0); + resolv_response_add_name (b, "127.in-addr.arpa"); + resolv_response_close_record (b); + + resolv_response_open_record (b, qname, qclass, 46 /* RRSIG */, 0); + { + char buf[500]; + memset (buf, 0x3f, sizeof (buf)); + resolv_response_add_data (b, buf, sizeof (buf)); + } + resolv_response_close_record (b); + + resolv_response_open_record (b, qname, qclass, T_PTR, 0); + resolv_response_add_name (b, "0.in-addr.arpa"); + resolv_response_close_record (b); + break; + case 7: + /* Test CNAME handling. */ + resolv_response_init (b, (struct resolv_response_flags) { }); + resolv_response_add_question (b, qname, qclass, qtype); + resolv_response_section (b, ns_s_an); + resolv_response_open_record (b, qname, qclass, T_CNAME, 0); + resolv_response_add_name (b, "cname.example"); + resolv_response_close_record (b); + resolv_response_open_record (b, "cname.example", qclass, T_PTR, 0); + resolv_response_add_name (b, "4.3.2.1.in-addr.arpa"); + resolv_response_close_record (b); + break; + + case 100: + resolv_response_init (b, (struct resolv_response_flags) { .rcode = 0, }); + resolv_response_add_question (b, qname, qclass, qtype); + break; + case 101: + resolv_response_init (b, (struct resolv_response_flags) + { .rcode = NXDOMAIN, }); + resolv_response_add_question (b, qname, qclass, qtype); + break; + case 102: + resolv_response_init (b, (struct resolv_response_flags) {.rcode = SERVFAIL}); + resolv_response_add_question (b, qname, qclass, qtype); + break; + case 103: + /* Check response length matching. */ + if (!ctx->tcp) + { + resolv_response_init (b, (struct resolv_response_flags) {.tc = true}); + resolv_response_add_question (b, qname, qclass, qtype); + } + else + { + resolv_response_init (b, (struct resolv_response_flags) {.ancount = 1}); + resolv_response_add_question (b, qname, qclass, qtype); + resolv_response_section (b, ns_s_an); + resolv_response_open_record (b, qname, qclass, T_PTR, 0); + resolv_response_add_name (b, "127.in-addr.arpa"); + resolv_response_close_record (b); + resolv_response_open_record (b, qname, qclass, T_PTR, 0); + resolv_response_add_name (b, "example"); + resolv_response_close_record (b); + + resolv_response_open_record (b, qname, qclass, T_PTR, 0); + size_t to_fill = 65535 - resolv_response_length (b) + - 2 /* length, "n" */ - 2 /* compression reference */ + - 2 /* RR type */; + for (size_t i = 0; i < to_fill; ++i) + resolv_response_add_data (b, "", 1); + resolv_response_close_record (b); + resolv_response_add_name (b, "n.example"); + uint16_t rrtype = htons (T_PTR); + resolv_response_add_data (b, &rrtype, sizeof (rrtype)); + } + break; + default: + FAIL_EXIT1 ("invalid QNAME: %s (code %d)", qname, code); + } +} + +static void +response (const struct resolv_response_context *ctx, + struct resolv_response_builder *b, + const char *qname, uint16_t qclass, uint16_t qtype) +{ + int code; + if (strstr (qname, "in-addr.arpa") == NULL) + { + char *tail; + if (sscanf (qname, "code%d.%ms", &code, &tail) != 2 + || strcmp (tail, "example") != 0) + FAIL_EXIT1 ("invalid QNAME: %s", qname); + free (tail); + handle_code (ctx, b, qname, qclass, qtype, code); + } + else + { + /* Reverse lookup. */ + int components[4]; + char *tail; + if (sscanf (qname, "%d.%d.%d.%d.%ms", + components, components + 1, components + 2, components + 3, + &tail) != 5 + || strcmp (tail, "in-addr.arpa") != 0) + FAIL_EXIT1 ("invalid QNAME: %s", qname); + free (tail); + handle_code (ctx, b, qname, qclass, qtype, components[3]); + } +} + +static void +check_reverse (int code, const char *expected) +{ + char *query = xasprintf ("code=%d", code); + check_netent (query, getnetbyaddr (code, AF_INET), expected); + free (query); +} + +/* Test for CVE-2016-3075. */ +static void +check_long_name (void) +{ + struct xmemstream mem; + xopen_memstream (&mem); + + char label[65]; + memset (label, 'x', 63); + label[63] = '.'; + label[64] = '\0'; + for (unsigned i = 0; i < 64 * 1024 * 1024 / strlen (label); ++i) + fprintf (mem.out, "%s", label); + + xfclose_memstream (&mem); + + check_netent ("long name", getnetbyname (mem.buffer), + "error: NO_RECOVERY\n"); + + free (mem.buffer); +} + +int +main (void) +{ + struct resolv_test *obj = resolv_test_start + ((struct resolv_redirect_config) + { + .response_callback = response + }); + + /* Lookup by name, success cases. */ + check_netent ("code1.example", getnetbyname ("code1.example"), + "alias: 1.in-addr.arpa\n" + "net: 0x00000001\n"); + check_netent ("code2.example", getnetbyname ("code2.example"), + "alias: 2.1.in-addr.arpa\n" + "net: 0x00000102\n"); + check_netent ("code3.example", getnetbyname ("code3.example"), + "alias: 3.2.1.in-addr.arpa\n" + "net: 0x00010203\n"); + check_netent ("code4.example", getnetbyname ("code4.example"), + "alias: 4.3.2.1.in-addr.arpa\n" + "net: 0x01020304\n"); + check_netent ("code5.example", getnetbyname ("code5.example"), + "alias: 127.in-addr.arpa\n" + "alias: 0.in-addr.arpa\n" + "net: 0x0000007f\n"); + check_netent ("code6.example", getnetbyname ("code6.example"), + "alias: 127.in-addr.arpa\n" + "alias: 0.in-addr.arpa\n" + "net: 0x0000007f\n"); + check_netent ("code7.example", getnetbyname ("code7.example"), + "alias: 4.3.2.1.in-addr.arpa\n" + "net: 0x01020304\n"); + + /* Lookup by name, failure cases. */ + check_netent ("code100.example", getnetbyname ("code100.example"), + "error: NO_ADDRESS\n"); + check_netent ("code101.example", getnetbyname ("code101.example"), + "error: HOST_NOT_FOUND\n"); + check_netent ("code102.example", getnetbyname ("code102.example"), + "error: TRY_AGAIN\n"); + check_netent ("code103.example", getnetbyname ("code103.example"), + "error: NO_RECOVERY\n"); + + /* Lookup by address, success cases. */ + check_reverse (1, + "name: 1.in-addr.arpa\n" + "net: 0x00000001\n"); + check_reverse (2, + "name: 2.1.in-addr.arpa\n" + "net: 0x00000002\n"); + check_reverse (3, + "name: 3.2.1.in-addr.arpa\n" + "net: 0x00000003\n"); + check_reverse (4, + "name: 4.3.2.1.in-addr.arpa\n" + "net: 0x00000004\n"); + check_reverse (5, + "name: 127.in-addr.arpa\n" + "alias: 0.in-addr.arpa\n" + "net: 0x00000005\n"); + check_reverse (6, + "name: 127.in-addr.arpa\n" + "alias: 0.in-addr.arpa\n" + "net: 0x00000006\n"); + check_reverse (7, + "name: 4.3.2.1.in-addr.arpa\n" + "net: 0x00000007\n"); + + /* Lookup by address, failure cases. */ + check_reverse (100, + "error: NO_ADDRESS\n"); + check_reverse (101, + "error: HOST_NOT_FOUND\n"); + check_reverse (102, + "error: TRY_AGAIN\n"); + check_reverse (103, + "error: NO_RECOVERY\n"); + + check_long_name (); + + resolv_test_end (obj); +} diff --git a/resolv/tst-resolv-search.c b/resolv/tst-resolv-search.c new file mode 100644 index 0000000..d95f9d9 --- /dev/null +++ b/resolv/tst-resolv-search.c @@ -0,0 +1,343 @@ +/* Test search/default domain name behavior. + Copyright (C) 2016 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 + . */ + +#include +#include +#include +#include +#include +#include +#include + +struct item +{ + const char *name; + int response; +}; + +const struct item items[] = + { + {"hostname.usersys.example.com", 1}, + {"hostname.corp.example.com", 1}, + {"hostname.example.com", 1}, + + {"mail.corp.example.com", 1}, + {"mail.example.com", 1}, + + {"file.corp.example.com", 2}, + {"file.corp", 1}, + {"file.example.com", 1}, + {"servfail-usersys.usersys.example.com", -ns_r_servfail}, + {"servfail-usersys.corp.example.com", 1}, + {"servfail-usersys.example.com", 1}, + {"servfail-corp.usersys.example.com", 1}, + {"servfail-corp.corp.example.com", -ns_r_servfail}, + {"servfail-corp.example.com", 1}, + {"www.example.com", 1}, + {"large.example.com", 200}, + + /* Test query amplification with a SERVFAIL response combined with + a large RRset. */ + {"large-servfail.usersys.example.com", -ns_r_servfail}, + {"large-servfail.example.com", 2000}, + {} + }; + +enum + { + name_not_found = -1, + name_no_data = -2 + }; + +static int +find_name (const char *name) +{ + for (int i = 0; items[i].name != NULL; ++i) + { + if (strcmp (name, items[i].name) == 0) + return i; + } + if (strcmp (name, "example.com") == 0 + || strcmp (name, "usersys.example.com") == 0 + || strcmp (name, "corp.example.com") == 0) + return name_no_data; + return name_not_found; +} + +static int rcode_override_server_index = -1; +static int rcode_override; + +static void +response (const struct resolv_response_context *ctx, + struct resolv_response_builder *b, + const char *qname, uint16_t qclass, uint16_t qtype) +{ + if (ctx->server_index == rcode_override_server_index) + { + struct resolv_response_flags flags = {.rcode = rcode_override}; + resolv_response_init (b, flags); + resolv_response_add_question (b, qname, qclass, qtype); + return; + } + + int index = find_name (qname); + struct resolv_response_flags flags = {}; + if (index == name_not_found) + flags.rcode = ns_r_nxdomain; + else if (index >= 0 && items[index].response < 0) + flags.rcode = -items[index].response; + else if (index >= 0 && items[index].response > 5 && !ctx->tcp) + /* Force TCP if more than 5 addresses where requested. */ + flags.tc = true; + resolv_response_init (b, flags); + resolv_response_add_question (b, qname, qclass, qtype); + + if (flags.tc || index < 0 || items[index].response < 0) + return; + + resolv_response_section (b, ns_s_an); + + for (int i = 0; i < items[index].response; ++i) + { + resolv_response_open_record (b, qname, qclass, qtype, 0); + + switch (qtype) + { + case T_A: + { + char addr[4] = {10, index, i >> 8, i}; + resolv_response_add_data (b, addr, sizeof (addr)); + } + break; + case T_AAAA: + { + char addr[16] + = {0x20, 0x01, 0xd, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, + 0, index + 1, (i + 1) >> 8, i + 1}; + resolv_response_add_data (b, addr, sizeof (addr)); + } + break; + default: + support_record_failure (); + printf ("error: unexpected QTYPE: %s/%u/%u\n", + qname, qclass, qtype); + } + resolv_response_close_record (b); + } +} + +enum output_format + { + format_get, format_gai + }; + +static void +format_expected_1 (FILE *out, int family, enum output_format format, int index) +{ + for (int i = 0; i < items[index].response; ++i) + { + char address[200]; + switch (family) + { + case AF_INET: + snprintf (address, sizeof (address), "10.%d.%d.%d", + index, (i >> 8) & 0xff, i & 0xff); + break; + case AF_INET6: + snprintf (address, sizeof (address), "2001:db8::%x:%x", + index + 1, i + 1); + break; + default: + FAIL_EXIT1 ("unreachable"); + } + + switch (format) + { + case format_get: + fprintf (out, "address: %s\n", address); + break; + case format_gai: + fprintf (out, "address: STREAM/TCP %s 80\n", address); + } + } +} + +static char * +format_expected (const char *fqdn, int family, enum output_format format) +{ + int index = find_name (fqdn); + TEST_VERIFY_EXIT (index >= 0); + struct xmemstream stream; + xopen_memstream (&stream); + + TEST_VERIFY_EXIT (items[index].response >= 0); + if (format == format_get) + fprintf (stream.out, "name: %s\n", items[index].name); + if (family == AF_INET || family == AF_UNSPEC) + format_expected_1 (stream.out, AF_INET, format, index); + if (family == AF_INET6 || family == AF_UNSPEC) + format_expected_1 (stream.out, AF_INET6, format, index); + + xfclose_memstream (&stream); + return stream.buffer; +} + +static void +do_get (const char *name, const char *fqdn, int family) +{ + char *expected = format_expected (fqdn, family, format_get); + if (family == AF_INET) + { + char *query = xasprintf ("gethostbyname (\"%s\")", name); + check_hostent (query, gethostbyname (name), expected); + free (query); + } + char *query = xasprintf ("gethostbyname2 (\"%s\", %d)", name, family); + check_hostent (query, gethostbyname2 (name, family), expected); + + /* Test res_search. */ + int qtype; + switch (family) + { + case AF_INET: + qtype = T_A; + break; + case AF_INET6: + qtype = T_AAAA; + break; + default: + qtype = -1; + } + if (qtype >= 0) + { + int sz = 512; + unsigned char *response = xmalloc (sz); + int ret = res_search (name, C_IN, qtype, response, sz); + TEST_VERIFY_EXIT (ret >= 0); + if (ret > sz) + { + /* Truncation. Retry with a larger buffer. */ + sz = 65535; + unsigned char *newptr = xrealloc (response, sz); + response = newptr; + + ret = res_search (name, C_IN, qtype, response, sz); + TEST_VERIFY_EXIT (ret >= 0); + TEST_VERIFY_EXIT (ret < sz); + } + check_dns_packet (query, response, ret, expected); + free (response); + } + + free (query); + free (expected); +} + +static void +do_gai (const char *name, const char *fqdn, int family) +{ + struct addrinfo hints = + { + .ai_family = family, + .ai_protocol = IPPROTO_TCP, + .ai_socktype = SOCK_STREAM + }; + struct addrinfo *ai; + char *query = xasprintf ("%s:80 [%d]", name, family); + int ret = getaddrinfo (name, "80", &hints, &ai); + char *expected = format_expected (fqdn, family, format_gai); + check_addrinfo (query, ai, ret, expected); + if (ret == 0) + freeaddrinfo (ai); + free (expected); + free (query); +} + +static void +do_both (const char *name, const char *fqdn) +{ + do_get (name, fqdn, AF_INET); + do_get (name, fqdn, AF_INET6); + do_gai (name, fqdn, AF_INET); + do_gai (name, fqdn, AF_INET6); + do_gai (name, fqdn, AF_UNSPEC); +} + +static void +do_test_all (bool unconnectable_server) +{ + struct resolv_redirect_config config = + { + .response_callback = response, + .search = {"usersys.example.com", "corp.example.com", "example.com"}, + }; + struct resolv_test *obj = resolv_test_start (config); + + if (unconnectable_server) + { + /* 255.255.255.255 results in an immediate connect failure. The + next server will supply the answer instead. This is a + triggering condition for bug 19791. */ + _res.nsaddr_list[0].sin_addr.s_addr = -1; + _res.nsaddr_list[0].sin_port = htons (53); + } + + do_both ("file", "file.corp.example.com"); + do_both ("www", "www.example.com"); + do_both ("servfail-usersys", "servfail-usersys.corp.example.com"); + do_both ("servfail-corp", "servfail-corp.usersys.example.com"); + do_both ("large", "large.example.com"); + do_both ("large-servfail", "large-servfail.example.com"); + do_both ("file.corp", "file.corp"); + + /* Check that SERVFAIL and REFUSED responses do not alter the search + path resolution. */ + rcode_override_server_index = 0; + rcode_override = ns_r_servfail; + do_both ("hostname", "hostname.usersys.example.com"); + do_both ("large", "large.example.com"); + do_both ("large-servfail", "large-servfail.example.com"); + rcode_override = ns_r_refused; + do_both ("hostname", "hostname.usersys.example.com"); + do_both ("large", "large.example.com"); + do_both ("large-servfail", "large-servfail.example.com"); + /* Likewise, but with an NXDOMAIN for the first search path + entry. */ + rcode_override = ns_r_servfail; + do_both ("mail", "mail.corp.example.com"); + rcode_override = ns_r_refused; + do_both ("mail", "mail.corp.example.com"); + /* Likewise, but with ndots handling. */ + rcode_override = ns_r_servfail; + do_both ("file.corp", "file.corp"); + rcode_override = ns_r_refused; + do_both ("file.corp", "file.corp"); + + resolv_test_end (obj); +} + +static int +do_test (void) +{ + for (int unconnectable_server = 0; unconnectable_server < 2; + ++unconnectable_server) + do_test_all (unconnectable_server); + return 0; +} + +#include diff --git a/support/Makefile b/support/Makefile index 9544a08..7e34fcb 100644 --- a/support/Makefile +++ b/support/Makefile @@ -26,21 +26,42 @@ extra-libs-noinstall := $(extra-libs) libsupport-routines = \ check \ + check_addrinfo \ + check_dns_packet \ + check_hostent \ + check_netent \ delayed_exit \ ignore_stderr \ oom_error \ + resolv_test \ set_fortify_handler \ support_become_root \ support_enter_network_namespace \ + support_format_address_family \ + support_format_addrinfo \ + support_format_dns_packet \ + support_format_herrno \ + support_format_hostent \ + support_format_netent \ support_record_failure \ + support_run_diff \ support_test_main \ support_test_verify_impl \ temp_file \ write_message \ + xaccept \ xasprintf \ + xbind \ xcalloc \ + xconnect \ + xfclose \ + xfopen \ xfork \ + xgetsockname \ + xlisten \ xmalloc \ + xmemstream \ + xpoll \ xpthread_barrier_destroy \ xpthread_barrier_init \ xpthread_barrier_wait \ @@ -52,12 +73,18 @@ libsupport-routines = \ xpthread_join \ xpthread_mutex_lock \ xpthread_mutex_unlock \ + xpthread_once \ xpthread_sigmask \ xpthread_spin_lock \ xpthread_spin_unlock \ xrealloc \ + xrecvfrom \ + xsendto \ + xsetsockopt \ xsocket \ + xstrdup \ xwaitpid \ + xwrite \ libsupport-static-only-routines := $(libsupport-routines) # Only build one variant of the library. diff --git a/support/check_addrinfo.c b/support/check_addrinfo.c new file mode 100644 index 0000000..793b34f --- /dev/null +++ b/support/check_addrinfo.c @@ -0,0 +1,42 @@ +/* Compare struct addrinfo values against a formatted string. + Copyright (C) 2016 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 + . */ + +#include + +#include +#include +#include +#include +#include + +void +check_addrinfo (const char *query_description, struct addrinfo *ai, int ret, + const char *expected) +{ + char *formatted = support_format_addrinfo (ai, ret); + if (strcmp (formatted, expected) != 0) + { + support_record_failure (); + printf ("error: addrinfo comparison failure\n"); + if (query_description != NULL) + printf ("query: %s\n", query_description); + support_run_diff ("expected", expected, + "actual", formatted); + } + free (formatted); +} diff --git a/support/check_dns_packet.c b/support/check_dns_packet.c new file mode 100644 index 0000000..a2c16b1 --- /dev/null +++ b/support/check_dns_packet.c @@ -0,0 +1,42 @@ +/* Check that a DNS packet buffer has the expected contents. + Copyright (C) 2016 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 + . */ + +#include + +#include +#include +#include +#include +#include + +void +check_dns_packet (const char *query_description, + const unsigned char *buffer, size_t length, + const char *expected) +{ + char *formatted = support_format_dns_packet (buffer, length); + if (strcmp (formatted, expected) != 0) + { + support_record_failure (); + printf ("error: packet comparison failure\n"); + if (query_description != NULL) + printf ("query: %s\n", query_description); + support_run_diff ("expected", expected, "actual", formatted); + } + free (formatted); +} diff --git a/support/check_hostent.c b/support/check_hostent.c new file mode 100644 index 0000000..b0661b1 --- /dev/null +++ b/support/check_hostent.c @@ -0,0 +1,42 @@ +/* Compare struct hostent values against a formatted string. + Copyright (C) 2016 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 + . */ + +#include + +#include +#include +#include +#include +#include + +void +check_hostent (const char *query_description, struct hostent *h, + const char *expected) +{ + char *formatted = support_format_hostent (h); + if (strcmp (formatted, expected) != 0) + { + support_record_failure (); + printf ("error: hostent comparison failure\n"); + if (query_description != NULL) + printf ("query: %s\n", query_description); + support_run_diff ("expected", expected, + "actual", formatted); + } + free (formatted); +} diff --git a/support/check_netent.c b/support/check_netent.c new file mode 100644 index 0000000..f208624 --- /dev/null +++ b/support/check_netent.c @@ -0,0 +1,42 @@ +/* Compare struct netent values against a formatted string. + Copyright (C) 2016 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 + . */ + +#include + +#include +#include +#include +#include +#include + +void +check_netent (const char *query_description, struct netent *e, + const char *expected) +{ + char *formatted = support_format_netent (e); + if (strcmp (formatted, expected) != 0) + { + support_record_failure (); + printf ("error: netent comparison failure\n"); + if (query_description != NULL) + printf ("query: %s\n", query_description); + support_run_diff ("expected", expected, + "actual", formatted); + } + free (formatted); +} diff --git a/support/check_nss.h b/support/check_nss.h new file mode 100644 index 0000000..6ab2824 --- /dev/null +++ b/support/check_nss.h @@ -0,0 +1,42 @@ +/* Test verification functions for NSS- and DNS-related data. + Copyright (C) 2016 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 + . */ + +#ifndef SUPPORT_CHECK_NSS_H +#define SUPPORT_CHECK_NSS_H + +#include +#include + +__BEGIN_DECLS + +/* Compare the data structures against the expected values (which have + to be formatted according to the support_format_* functions in + ). If there is a difference, a delayed test + failure is recorded, and a diff is written to standard output. */ +void check_addrinfo (const char *query_description, + struct addrinfo *, int ret, const char *expected); +void check_dns_packet (const char *query_description, + const unsigned char *, size_t, const char *expected); +void check_hostent (const char *query_description, + struct hostent *, const char *expected); +void check_netent (const char *query_description, + struct netent *, const char *expected); + +__END_DECLS + +#endif /* SUPPORT_CHECK_NSS_H */ diff --git a/support/format_nss.h b/support/format_nss.h new file mode 100644 index 0000000..07e32f5 --- /dev/null +++ b/support/format_nss.h @@ -0,0 +1,41 @@ +/* String formatting functions for NSS- and DNS-related data. + Copyright (C) 2016 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 + . */ + +#ifndef SUPPORT_FORMAT_NSS_H +#define SUPPORT_FORMAT_NSS_H + +#include +#include + +__BEGIN_DECLS + +/* The following functions format their arguments as human-readable + strings (which can span multiple lines). The caller must free the + returned buffer. For NULL pointers or failure status arguments, + error variables such as h_errno and errno are included in the + result. */ +char *support_format_address_family (int); +char *support_format_addrinfo (struct addrinfo *, int ret); +char *support_format_dns_packet (const unsigned char *buffer, size_t length); +char *support_format_herrno (int); +char *support_format_hostent (struct hostent *); +char *support_format_netent (struct netent *); + +__END_DECLS + +#endif /* SUPPORT_FORMAT_NSS_H */ diff --git a/support/resolv_test.c b/support/resolv_test.c new file mode 100644 index 0000000..80d8c74 --- /dev/null +++ b/support/resolv_test.c @@ -0,0 +1,1150 @@ +/* DNS test framework and libresolv redirection. + Copyright (C) 2016 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 + . */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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; +}; + +/* Update *INFO from the specified DNS packet. */ +static void +parse_query (struct query_info *info, + const unsigned char *buffer, size_t length) +{ + if (length < 12) + FAIL_EXIT1 ("malformed DNS query: too short: %zu bytes", length); + + int ret = dn_expand (buffer, buffer + length, buffer + 12, + 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); +} + + +/* 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, + }; + 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, + }; + 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; + } + + close (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); + close (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) + { + close (server->socket_udp); + close (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) + { + close (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) + { + close (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)); + close (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)); + close (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); + close (obj->servers[server_index].socket_udp); + } + if (!obj->config.servers[server_index].disable_tcp) + { + xpthread_join (obj->servers[server_index].thread_tcp); + close (obj->servers[server_index].socket_tcp); + } + } + + free (obj); +} diff --git a/support/resolv_test.h b/support/resolv_test.h new file mode 100644 index 0000000..39dd625 --- /dev/null +++ b/support/resolv_test.h @@ -0,0 +1,169 @@ +/* DNS test framework and libresolv redirection. + Copyright (C) 2016 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 + . */ + +#ifndef SUPPORT_RESOLV_TEST_H +#define SUPPORT_RESOLV_TEST_H + +#include +#include +#include + +__BEGIN_DECLS + +/* This struct provides context information when the response callback + specified in struct resolv_redirect_config is invoked. */ +struct resolv_response_context +{ + const unsigned char *query_buffer; + size_t query_length; + int server_index; + bool tcp; +}; + +/* This opaque struct is used to construct responses from within the + response callback function. */ +struct resolv_response_builder; + +/* This opaque struct collects information about the resolver testing + currently in progress. */ +struct resolv_test; + +enum + { + /* Maximum number of test servers supported by the framework. */ + resolv_max_test_servers = 3, + }; + +/* Configuration settings specific to individual test servers. */ +struct resolv_redirect_server_config +{ + bool disable_tcp; /* If true, no TCP server is listening. */ + bool disable_udp; /* If true, no UDP server is listening. */ +}; + +/* Instructions for setting up the libresolv redirection. */ +struct resolv_redirect_config +{ + /* The response_callback function is called for every incoming DNS + packet, over UDP or TCP. It must be specified, the other + configuration settings are optional. */ + void (*response_callback) (const struct resolv_response_context *, + struct resolv_response_builder *, + const char *qname, + uint16_t qclass, uint16_t qtype); + + /* Per-server configuration. */ + struct resolv_redirect_server_config servers[resolv_max_test_servers]; + + /* Search path entries. The first entry serves as the default + domain name as well. */ + const char *search[7]; + + /* Number of servers to activate in resolv. 0 means the default, + resolv_max_test_servers. */ + int nscount; + + /* If true, use a single thread to process all UDP queries. This + may results in more predictable ordering of queries and + responses. */ + bool single_thread_udp; +}; + +/* Configure NSS to use, nss_dns only for aplicable databases, and try + to put the process into a network namespace for better isolation. + This may have to be called before resolv_test_start, before the + process creates any threads. Otherwise, initialization is + performed by resolv_test_start implicitly. */ +void resolv_test_init (void); + +/* Initiate resolver testing. This updates the _res variable as + needed. As a side effect, NSS is reconfigured to use nss_dns only + for aplicable databases, and the process may enter a network + namespace for better isolation. */ +struct resolv_test *resolv_test_start (struct resolv_redirect_config); + +/* Call this function at the end of resolver testing, to free + resources and report pending errors (if any). */ +void resolv_test_end (struct resolv_test *); + +/* The remaining facilities in this file are used for constructing + response packets from the response_callback function. */ + +/* Special settings for constructing responses from the callback. */ +struct resolv_response_flags +{ + /* 4-bit response code to incorporate into the response. */ + unsigned char rcode; + + /* If true, the TC (truncation) flag will be set. */ + bool tc; + + /* Initial section count values. Can be used to artificially + increase the counts, for malformed packet testing.*/ + unsigned short qdcount; + unsigned short ancount; + unsigned short nscount; + unsigned short adcount; +}; + +/* Begin a new response with the requested flags. Must be called + first. */ +void resolv_response_init (struct resolv_response_builder *, + struct resolv_response_flags); + +/* Switches to the section in the response packet. Only forward + movement is supported. */ +void resolv_response_section (struct resolv_response_builder *, ns_sect); + +/* Add a question record to the question section. */ +void resolv_response_add_question (struct resolv_response_builder *, + const char *name, uint16_t class, + uint16_t type); +/* Starts a new resource record with the specified owner name, class, + type, and TTL. Data is supplied with resolv_response_add_data or + resolv_response_add_name. */ +void resolv_response_open_record (struct resolv_response_builder *, + const char *name, uint16_t class, + uint16_t type, uint32_t ttl); + +/* Add unstructed bytes to the RDATA part of a resource record. */ +void resolv_response_add_data (struct resolv_response_builder *, + const void *, size_t); + +/* Add a compressed domain name to the RDATA part of a resource + record. */ +void resolv_response_add_name (struct resolv_response_builder *, + const char *name); + +/* Mark the end of the constructed record. Must be called last. */ +void resolv_response_close_record (struct resolv_response_builder *); + +/* Drop this query packet (that is, do not send a response, not even + an empty packet). */ +void resolv_response_drop (struct resolv_response_builder *); + +/* In TCP mode, close the connection after this packet (if a response + is sent). */ +void resolv_response_close (struct resolv_response_builder *); + +/* The size of the response packet built so far. */ +size_t resolv_response_length (const struct resolv_response_builder *); + +__END_DECLS + +#endif /* SUPPORT_RESOLV_TEST_H */ diff --git a/support/run_diff.h b/support/run_diff.h new file mode 100644 index 0000000..af340ef --- /dev/null +++ b/support/run_diff.h @@ -0,0 +1,31 @@ +/* Invoke the system diff tool to compare two strings. + Copyright (C) 2016 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 + . */ + +#ifndef SUPPORT_RUN_DIFF_H +#define SUPPORT_RUN_DIFF_H + +/* Compare the two NUL-terminated strings LEFT and RIGHT using the + diff tool. Label the sides of the diff with LEFT_LABEL and + RIGHT_LABEL, respectively. + + This function assumes that LEFT and RIGHT are different + strings. */ +void support_run_diff (const char *left_label, const char *left, + const char *right_label, const char *right); + +#endif /* SUPPORT_RUN_DIFF_H */ diff --git a/support/support.h b/support/support.h index fc7fba9..136e0ce 100644 --- a/support/support.h +++ b/support/support.h @@ -52,6 +52,7 @@ void *xcalloc (size_t n, size_t s) __attribute__ ((malloc)); void *xrealloc (void *p, size_t n); char *xasprintf (const char *format, ...) __attribute__ ((format (printf, 1, 2), malloc)); +char *xstrdup (const char *); __END_DECLS diff --git a/support/support_format_address_family.c b/support/support_format_address_family.c new file mode 100644 index 0000000..06c6af7 --- /dev/null +++ b/support/support_format_address_family.c @@ -0,0 +1,35 @@ +/* Convert an address family to a string. + Copyright (C) 2016 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 + . */ + +#include + +#include + +char * +support_format_address_family (int family) +{ + switch (family) + { + case AF_INET: + return xstrdup ("INET"); + case AF_INET6: + return xstrdup ("INET6"); + default: + return xasprintf ("", family); + } +} diff --git a/support/support_format_addrinfo.c b/support/support_format_addrinfo.c new file mode 100644 index 0000000..744aad4 --- /dev/null +++ b/support/support_format_addrinfo.c @@ -0,0 +1,202 @@ +/* Convert struct addrinfo values to a string. + Copyright (C) 2016 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 + . */ + +#include + +#include +#include +#include +#include +#include + +static size_t +socket_address_length (int family) +{ + switch (family) + { + case AF_INET: + return sizeof (struct sockaddr_in); + case AF_INET6: + return sizeof (struct sockaddr_in6); + default: + return -1; + } +} + +static void +format_ai_flags (FILE *out, struct addrinfo *ai, int flag, const char *name, + int * flags_printed) +{ + if ((ai->ai_flags & flag) != 0) + fprintf (out, " %s", name); + *flags_printed |= flag; +} + +static void +format_ai_one (FILE *out, struct addrinfo *ai, int *flags) +{ + /* ai_flags */ + if (ai->ai_flags != *flags) + { + fprintf (out, "flags:"); + int flags_printed = 0; +#define FLAG(flag) format_ai_flags (out, ai, flag, #flag, &flags_printed) + FLAG (AI_PASSIVE); + FLAG (AI_CANONNAME); + FLAG (AI_NUMERICHOST); + FLAG (AI_V4MAPPED); + FLAG (AI_ALL); + FLAG (AI_ADDRCONFIG); + FLAG (AI_IDN); + FLAG (AI_CANONIDN); + FLAG (AI_IDN_ALLOW_UNASSIGNED); + FLAG (AI_IDN_USE_STD3_ASCII_RULES); + FLAG (AI_NUMERICSERV); +#undef FLAG + int remaining = ai->ai_flags & ~flags_printed; + if (remaining != 0) + fprintf (out, " %08x", remaining); + fprintf (out, "\n"); + *flags = ai->ai_flags; + } + + { + char type_buf[32]; + const char *type_str; + char proto_buf[32]; + const char *proto_str; + + /* ai_socktype */ + switch (ai->ai_socktype) + { + case SOCK_RAW: + type_str = "RAW"; + break; + case SOCK_DGRAM: + type_str = "DGRAM"; + break; + case SOCK_STREAM: + type_str = "STREAM"; + break; + default: + snprintf (type_buf, sizeof (type_buf), "%d", ai->ai_socktype); + type_str = type_buf; + } + + /* ai_protocol */ + switch (ai->ai_protocol) + { + case IPPROTO_IP: + proto_str = "IP"; + break; + case IPPROTO_UDP: + proto_str = "UDP"; + break; + case IPPROTO_TCP: + proto_str = "TCP"; + break; + default: + snprintf (proto_buf, sizeof (proto_buf), "%d", ai->ai_protocol); + proto_str = proto_buf; + } + fprintf (out, "address: %s/%s", type_str, proto_str); + } + + /* ai_addrlen */ + if (ai->ai_addrlen != socket_address_length (ai->ai_family)) + { + char *family = support_format_address_family (ai->ai_family); + fprintf (out, "error: invalid address length %d for %s\n", + ai->ai_addrlen, family); + free (family); + } + + /* ai_addr */ + { + char buf[128]; + uint16_t port; + const char *ret; + switch (ai->ai_family) + { + case AF_INET: + { + struct sockaddr_in *sin = (struct sockaddr_in *) ai->ai_addr; + ret = inet_ntop (AF_INET, &sin->sin_addr, buf, sizeof (buf)); + port = sin->sin_port; + } + break; + case AF_INET6: + { + struct sockaddr_in6 *sin = (struct sockaddr_in6 *) ai->ai_addr; + ret = inet_ntop (AF_INET6, &sin->sin6_addr, buf, sizeof (buf)); + port = sin->sin6_port; + } + break; + default: + errno = EAFNOSUPPORT; + ret = NULL; + } + if (ret == NULL) + fprintf (out, "error: inet_top failed: %m\n"); + else + fprintf (out, " %s %u\n", buf, ntohs (port)); + } + + /* ai_canonname */ + if (ai->ai_canonname != NULL) + fprintf (out, "canonname: %s\n", ai->ai_canonname); +} + +/* Format all the addresses in one address family. */ +static void +format_ai_family (FILE *out, struct addrinfo *ai, int family, int *flags) +{ + while (ai) + { + if (ai->ai_family == family) + format_ai_one (out, ai, flags); + ai = ai->ai_next; + } +} + +char * +support_format_addrinfo (struct addrinfo *ai, int ret) +{ + int errno_copy = errno; + + struct xmemstream mem; + xopen_memstream (&mem); + if (ret != 0) + { + fprintf (mem.out, "error: %s\n", gai_strerror (ret)); + if (ret == EAI_SYSTEM) + { + errno = errno_copy; + fprintf (mem.out, "error: %m\n"); + } + } + else + { + int flags = 0; + format_ai_family (mem.out, ai, AF_INET, &flags); + format_ai_family (mem.out, ai, AF_INET6, &flags); + } + + xfclose_memstream (&mem); + return mem.buffer; +} diff --git a/support/support_format_dns_packet.c b/support/support_format_dns_packet.c new file mode 100644 index 0000000..d073ce0 --- /dev/null +++ b/support/support_format_dns_packet.c @@ -0,0 +1,215 @@ +/* Convert a DNS packet to a human-readable representation. + Copyright (C) 2016 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 + . */ + +#include + +#include +#include +#include +#include +#include + +struct in_buffer +{ + const unsigned char *data; + size_t size; +}; + +static inline bool +extract_8 (struct in_buffer *in, unsigned char *value) +{ + if (in->size == 0) + return false; + *value = in->data[0]; + ++in->data; + --in->size; + return true; +} + +static inline bool +extract_16 (struct in_buffer *in, unsigned short *value) +{ + if (in->size < 2) + return false; + *value = (in->data[0] << 8) | in->data[1]; + in->data += 2; + in->size -= 2; + return true; +} + +static inline bool +extract_32 (struct in_buffer *in, unsigned *value) +{ + if (in->size < 4) + return false; + unsigned a = in->data[0]; + unsigned b = in->data[1]; + unsigned c = in->data[2]; + unsigned d = in->data[3]; + *value = (a << 24) | (b << 16) | (c << 8) | d; + in->data += 4; + in->size -= 4; + return true; +} + +static inline bool +extract_bytes (struct in_buffer *in, size_t length, struct in_buffer *value) +{ + if (in->size < length) + return false; + *value = (struct in_buffer) {in->data, length}; + in->data += length; + in->size -= length; + return true; +} + +struct dname +{ + char name[MAXDNAME + 1]; +}; + +static bool +extract_name (struct in_buffer full, struct in_buffer *in, struct dname *value) +{ + const unsigned char *full_end = full.data + full.size; + /* Sanity checks; these indicate buffer misuse. */ + TEST_VERIFY_EXIT + (!(in->data < full.data || in->data > full_end + || in->size > (size_t) (full_end - in->data))); + int ret = dn_expand (full.data, full_end, in->data, + value->name, sizeof (value->name)); + if (ret < 0) + return false; + in->data += ret; + in->size -= ret; + return true; +} + +char * +support_format_dns_packet (const unsigned char *buffer, size_t length) +{ + struct in_buffer full = { buffer, length }; + struct in_buffer in = full; + struct xmemstream mem; + xopen_memstream (&mem); + + unsigned short txnid; + unsigned short flags; + unsigned short qdcount; + unsigned short ancount; + unsigned short nscount; + unsigned short adcount; + if (!(extract_16 (&in, &txnid) + && extract_16 (&in, &flags) + && extract_16 (&in, &qdcount) + && extract_16 (&in, &ancount) + && extract_16 (&in, &nscount) + && extract_16 (&in, &adcount))) + { + fprintf (mem.out, "error: could not parse DNS header\n"); + goto out; + } + if (qdcount != 1) + { + fprintf (mem.out, "error: question count is %d, not 1\n", qdcount); + goto out; + } + struct dname qname; + if (!extract_name (full, &in, &qname)) + { + fprintf (mem.out, "error: malformed QNAME\n"); + goto out; + } + unsigned short qtype; + unsigned short qclass; + if (!(extract_16 (&in, &qtype) + && extract_16 (&in, &qclass))) + { + fprintf (mem.out, "error: malformed question\n"); + goto out; + } + if (qtype != T_A && qtype != T_AAAA && qtype != T_PTR) + { + fprintf (mem.out, "error: unsupported QTYPE %d\n", qtype); + goto out; + } + + fprintf (mem.out, "name: %s\n", qname.name); + + for (int i = 0; i < ancount; ++i) + { + struct dname rname; + if (!extract_name (full, &in, &rname)) + { + fprintf (mem.out, "error: malformed record name\n"); + goto out; + } + unsigned short rtype; + unsigned short rclass; + unsigned ttl; + unsigned short rdlen; + struct in_buffer rdata; + if (!(extract_16 (&in, &rtype) + && extract_16 (&in, &rclass) + && extract_32 (&in, &ttl) + && extract_16 (&in, &rdlen) + && extract_bytes (&in, rdlen, &rdata))) + { + fprintf (mem.out, "error: malformed record header\n"); + goto out; + } + /* Skip non-matching record types. */ + if (rtype != qtype || rclass != qclass) + continue; + switch (rtype) + { + case T_A: + if (rdlen == 4) + fprintf (mem.out, "address: %d.%d.%d.%d\n", + rdata.data[0], + rdata.data[1], + rdata.data[2], + rdata.data[3]); + else + fprintf (mem.out, "error: A record of size %d: %s\n", rdlen, rname.name); + break; + case T_AAAA: + { + char buf[100]; + if (inet_ntop (AF_INET6, rdata.data, buf, sizeof (buf)) == NULL) + fprintf (mem.out, "error: AAAA record decoding failed: %m\n"); + else + fprintf (mem.out, "address: %s\n", buf); + } + break; + case T_CNAME: + case T_PTR: + { + struct dname name; + if (extract_name (full, &in, &name)) + fprintf (mem.out, "name: %s\n", name.name); + else + fprintf (mem.out, "error: malformed CNAME/PTR record\n"); + } + } + } + + out: + xfclose_memstream (&mem); + return mem.buffer; +} diff --git a/support/support_format_herrno.c b/support/support_format_herrno.c new file mode 100644 index 0000000..42279f6 --- /dev/null +++ b/support/support_format_herrno.c @@ -0,0 +1,45 @@ +/* Convert a h_errno error code to a string. + Copyright (C) 2016 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 + . */ + +#include + +#include + +char * +support_format_herrno (int code) +{ + const char *errstr; + switch (code) + { + case HOST_NOT_FOUND: + errstr = "HOST_NOT_FOUND"; + break; + case NO_ADDRESS: + errstr = "NO_ADDRESS"; + break; + case NO_RECOVERY: + errstr = "NO_RECOVERY"; + break; + case TRY_AGAIN: + errstr = "TRY_AGAIN"; + break; + default: + return xasprintf ("\n", code); + } + return xstrdup (errstr); +} diff --git a/support/support_format_hostent.c b/support/support_format_hostent.c new file mode 100644 index 0000000..5622f69 --- /dev/null +++ b/support/support_format_hostent.c @@ -0,0 +1,75 @@ +/* Convert a struct hostent object to a string. + Copyright (C) 2016 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 + . */ + +#include + +#include +#include +#include +#include + +static int +address_length (int family) +{ + switch (family) + { + case AF_INET: + return 4; + case AF_INET6: + return 16; + } + return -1; +} + +char * +support_format_hostent (struct hostent *h) +{ + if (h == NULL) + { + char *value = support_format_herrno (h_errno); + char *result = xasprintf ("error: %s\n", value); + free (value); + return result; + } + + struct xmemstream mem; + xopen_memstream (&mem); + + fprintf (mem.out, "name: %s\n", h->h_name); + for (char **alias = h->h_aliases; *alias != NULL; ++alias) + fprintf (mem.out, "alias: %s\n", *alias); + for (unsigned i = 0; h->h_addr_list[i] != NULL; ++i) + { + char buf[128]; + if (inet_ntop (h->h_addrtype, h->h_addr_list[i], + buf, sizeof (buf)) == NULL) + fprintf (mem.out, "error: inet_ntop failed: %m\n"); + else + fprintf (mem.out, "address: %s\n", buf); + } + if (h->h_length != address_length (h->h_addrtype)) + { + char *family = support_format_address_family (h->h_addrtype); + fprintf (mem.out, "error: invalid address length %d for %s\n", + h->h_length, family); + free (family); + } + + xfclose_memstream (&mem); + return mem.buffer; +} diff --git a/support/support_format_netent.c b/support/support_format_netent.c new file mode 100644 index 0000000..51a3a6b --- /dev/null +++ b/support/support_format_netent.c @@ -0,0 +1,50 @@ +/* Convert a struct netent object to a string. + Copyright (C) 2016 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 + . */ + +#include + +#include +#include +#include +#include + +char * +support_format_netent (struct netent *e) +{ + if (e == NULL) + { + char *value = support_format_herrno (h_errno); + char *result = xasprintf ("error: %s\n", value); + free (value); + return result; + } + + struct xmemstream mem; + xopen_memstream (&mem); + + if (e->n_name != NULL) + fprintf (mem.out, "name: %s\n", e->n_name); + for (char **ap = e->n_aliases; *ap != NULL; ++ap) + fprintf (mem.out, "alias: %s\n", *ap); + if (e->n_addrtype != AF_INET) + fprintf (mem.out, "addrtype: %d\n", e->n_addrtype); + fprintf (mem.out, "net: 0x%08x\n", e->n_net); + + xfclose_memstream (&mem); + return mem.buffer; +} diff --git a/support/support_run_diff.c b/support/support_run_diff.c new file mode 100644 index 0000000..a963e01 --- /dev/null +++ b/support/support_run_diff.c @@ -0,0 +1,76 @@ +/* Invoke the system diff tool to compare two strings. + Copyright (C) 2016 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 + . */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +static char * +write_to_temp_file (const char *prefix, const char *str) +{ + char *template = xasprintf ("run_diff-%s", prefix); + char *name = NULL; + int fd = create_temp_file (template, &name); + TEST_VERIFY_EXIT (fd >= 0); + free (template); + xwrite (fd, str, strlen (str)); + TEST_VERIFY_EXIT (close (fd) == 0); + return name; +} + +void +support_run_diff (const char *left_label, const char *left, + const char *right_label, const char *right) +{ + /* Ensure that the diff command output is ordered properly with + standard output. */ + TEST_VERIFY_EXIT (fflush (stdout) == 0); + + char *left_path = write_to_temp_file ("left-diff", left); + char *right_path = write_to_temp_file ("right-diff", right); + + pid_t pid = xfork (); + if (pid == 0) + { + execlp ("diff", "diff", "-u", + "--label", left_label, "--label", right_label, + "--", left_path, right_path, + NULL); + _exit (17); + } + else + { + int status; + xwaitpid (pid, &status, 0); + if (!WIFEXITED (status) || WEXITSTATUS (status) != 1) + printf ("warning: could not run diff, exit status: %d\n" + "*** %s ***\n%s\n" + "*** %s ***\n%s\n", + status, left_label, left, right_label, right); + } + + free (right_path); + free (left_path); +} diff --git a/support/xaccept.c b/support/xaccept.c new file mode 100644 index 0000000..387fc9f --- /dev/null +++ b/support/xaccept.c @@ -0,0 +1,32 @@ +/* accept with error checking. + Copyright (C) 2016 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 + . */ + +#include + +#include +#include +#include + +int +xaccept (int fd, struct sockaddr *sa, socklen_t *salen) +{ + int clientfd = accept (fd, sa, salen); + if (clientfd < 0) + FAIL_EXIT1 ("accept (%d): %m", fd); + return clientfd; +} diff --git a/support/xbind.c b/support/xbind.c new file mode 100644 index 0000000..59e0c3a --- /dev/null +++ b/support/xbind.c @@ -0,0 +1,30 @@ +/* bind with error checking. + Copyright (C) 2016 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 + . */ + +#include + +#include +#include +#include + +void +xbind (int fd, const struct sockaddr *sa, socklen_t sa_len) +{ + if (bind (fd, sa, sa_len) != 0) + FAIL_EXIT1 ("bind (%d), family %d: %m", fd, sa->sa_family); +} diff --git a/support/xconnect.c b/support/xconnect.c new file mode 100644 index 0000000..b4fe94c --- /dev/null +++ b/support/xconnect.c @@ -0,0 +1,30 @@ +/* connect with error checking. + Copyright (C) 2016 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 + . */ + +#include + +#include +#include +#include + +void +xconnect (int fd, const struct sockaddr *sa, socklen_t sa_len) +{ + if (connect (fd, sa, sa_len) != 0) + FAIL_EXIT1 ("connect (%d), family %d: %m", fd, sa->sa_family); +} diff --git a/support/xfclose.c b/support/xfclose.c new file mode 100644 index 0000000..45ca08c --- /dev/null +++ b/support/xfclose.c @@ -0,0 +1,33 @@ +/* fclose with error checking. + Copyright (C) 2016 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 + . */ + +#include + +#include +#include + +void +xfclose (FILE *fp) +{ + if (ferror (fp)) + FAIL_EXIT1 ("stdio stream closed with pending errors"); + if (fflush (fp) != 0) + FAIL_EXIT1 ("fflush: %m"); + if (fclose (fp) != 0) + FAIL_EXIT1 ("fclose: %m"); +} diff --git a/support/xfopen.c b/support/xfopen.c new file mode 100644 index 0000000..436ab55 --- /dev/null +++ b/support/xfopen.c @@ -0,0 +1,31 @@ +/* fopen with error checking. + Copyright (C) 2016 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 + . */ + +#include + +#include +#include + +FILE * +xfopen (const char *path, const char *mode) +{ + FILE *fp = fopen (path, mode); + if (fp == NULL) + FAIL_EXIT1 ("could not open %s (mode \"%s\"): %m", path, mode); + return fp; +} diff --git a/support/xgetsockname.c b/support/xgetsockname.c new file mode 100644 index 0000000..c66981e --- /dev/null +++ b/support/xgetsockname.c @@ -0,0 +1,30 @@ +/* getsockname with error checking. + Copyright (C) 2016 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 + . */ + +#include + +#include +#include +#include + +void +xgetsockname (int fd, struct sockaddr *sa, socklen_t *plen) +{ + if (getsockname (fd, sa, plen) != 0) + FAIL_EXIT1 ("setsockopt (%d): %m", fd); +} diff --git a/support/xlisten.c b/support/xlisten.c new file mode 100644 index 0000000..e099e5b --- /dev/null +++ b/support/xlisten.c @@ -0,0 +1,30 @@ +/* listen with error checking. + Copyright (C) 2016 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 + . */ + +#include + +#include +#include +#include + +void +xlisten (int fd, int backlog) +{ + if (listen (fd, backlog) != 0) + FAIL_EXIT1 ("listen (%d, %d): %m", fd, backlog); +} diff --git a/support/xmemstream.c b/support/xmemstream.c new file mode 100644 index 0000000..697d7c7 --- /dev/null +++ b/support/xmemstream.c @@ -0,0 +1,42 @@ +/* Error-checking wrappers for memstream functions. + Copyright (C) 2016 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 + . */ + +#include + +#include +#include +#include +#include + +void +xopen_memstream (struct xmemstream *stream) +{ + int old_errno = errno; + *stream = (struct xmemstream) {}; + stream->out = open_memstream (&stream->buffer, &stream->length); + if (stream->out == NULL) + FAIL_EXIT1 ("open_memstream: %m"); + errno = old_errno; +} + +void +xfclose_memstream (struct xmemstream *stream) +{ + xfclose (stream->out); + stream->out = NULL; +} diff --git a/support/xmemstream.h b/support/xmemstream.h new file mode 100644 index 0000000..bb3394a --- /dev/null +++ b/support/xmemstream.h @@ -0,0 +1,49 @@ +/* Error-checking wrappers for memstream functions. + Copyright (C) 2016 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 + . */ + +#ifndef SUPPORT_XMEMSTREAM_H +#define SUPPORT_XMEMSTREAM_H + +#include +#include + +__BEGIN_DECLS + +/* Wrappers for other libc functions. */ +struct xmemstream +{ + FILE *out; + char *buffer; + size_t length; +}; + +/* Create a new in-memory stream. Initializes *STREAM. After this + function returns, STREAM->out is a file descriptor open for + writing. errno is preserved, so that the %m format specifier can + be used for writing to STREAM->out. */ +void xopen_memstream (struct xmemstream *stream); + +/* Closes STREAM->OUT. After this function returns, STREAM->buffer + and STREAM->length denote a memory range which contains the bytes + written to the output stream. The caller should free + STREAM->buffer. */ +void xfclose_memstream (struct xmemstream *stream); + +__END_DECLS + +#endif /* SUPPORT_XMEMSTREAM_H */ diff --git a/support/xpoll.c b/support/xpoll.c new file mode 100644 index 0000000..3aaecde --- /dev/null +++ b/support/xpoll.c @@ -0,0 +1,32 @@ +/* poll with error checking. + Copyright (C) 2016 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 + . */ + +#include + +#include +#include +#include + +int +xpoll (struct pollfd *fds, nfds_t nfds, int timeout) +{ + int ret = poll (fds, nfds, timeout); + if (ret < 0) + FAIL_EXIT1 ("poll: %m"); + return ret; +} diff --git a/support/xpthread_once.c b/support/xpthread_once.c new file mode 100644 index 0000000..6ba0c69 --- /dev/null +++ b/support/xpthread_once.c @@ -0,0 +1,25 @@ +/* pthread_once with error checking. + Copyright (C) 2016 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 + . */ + +#include + +void +xpthread_once (pthread_once_t *guard, void (*func) (void)) +{ + xpthread_check_return ("pthread_once", pthread_once (guard, func)); +} diff --git a/support/xrecvfrom.c b/support/xrecvfrom.c new file mode 100644 index 0000000..2cd473a --- /dev/null +++ b/support/xrecvfrom.c @@ -0,0 +1,33 @@ +/* recvfrom with error checking. + Copyright (C) 2016 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 + . */ + +#include + +#include +#include +#include + +size_t +xrecvfrom (int fd, void *buf, size_t buflen, int flags, + struct sockaddr *sa, socklen_t *salen) +{ + ssize_t ret = recvfrom (fd, buf, buflen, flags, sa, salen); + if (ret < 0) + FAIL_EXIT1 ("error: recvfrom (%d), %zu bytes buffer: %m", fd, buflen); + return ret; +} diff --git a/support/xsendto.c b/support/xsendto.c new file mode 100644 index 0000000..6963aa7 --- /dev/null +++ b/support/xsendto.c @@ -0,0 +1,35 @@ +/* sendto with error checking. + Copyright (C) 2016 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 + . */ + +#include + +#include +#include +#include + +void +xsendto (int fd, const void *buf, size_t buflen, int flags, + const struct sockaddr *sa, socklen_t salen) +{ + ssize_t ret = sendto (fd, buf, buflen, flags, sa, salen); + if (ret < 0) + FAIL_EXIT1 ("sendto (%d), %zu bytes, family %d: %m", + fd, buflen, sa->sa_family); + if (ret != buflen) + FAIL_EXIT1 ("sendto (%d) sent %zd bytes instead of %zu", fd, ret, buflen); +} diff --git a/support/xsetsockopt.c b/support/xsetsockopt.c new file mode 100644 index 0000000..8996554 --- /dev/null +++ b/support/xsetsockopt.c @@ -0,0 +1,31 @@ +/* setsockopt with error checking. + Copyright (C) 2016 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 + . */ + +#include + +#include +#include +#include + +void +xsetsockopt (int fd, int level, int name, const void *val, socklen_t vallen) +{ + if (setsockopt (fd, level, name, val, vallen) != 0) + FAIL_EXIT1 ("setsockopt (%d, %d, %d), %zu bytes: %m", + fd, level, name, (size_t) vallen); +} diff --git a/support/xsocket.h b/support/xsocket.h index e9ff49e..c376e09 100644 --- a/support/xsocket.h +++ b/support/xsocket.h @@ -19,9 +19,20 @@ #ifndef SUPPORT_XSOCKET_H #define SUPPORT_XSOCKET_H +#include #include #include int xsocket (int, int, int); +void xsetsockopt (int, int, int, const void *, socklen_t); +void xgetsockname (int, struct sockaddr *, socklen_t *); +void xconnect (int, const struct sockaddr *, socklen_t); +void xbind (int, const struct sockaddr *, socklen_t); +void xlisten (int, int); +int xaccept (int, struct sockaddr *, socklen_t *); +void xsendto (int, const void *, size_t, int, + const struct sockaddr *, socklen_t); +size_t xrecvfrom (int, void *, size_t, int, struct sockaddr *, socklen_t *); +int xpoll (struct pollfd *, nfds_t, int); #endif /* SUPPORT_XSOCKET_H */ diff --git a/support/xstdio.h b/support/xstdio.h new file mode 100644 index 0000000..99601ba --- /dev/null +++ b/support/xstdio.h @@ -0,0 +1,32 @@ +/* Error-checking wrappers for stdio functions. + Copyright (C) 2016 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 + . */ + +#ifndef SUPPORT_XSTDIO_H +#define SUPPORT_XSTDIO_H + +#include +#include + +__BEGIN_DECLS + +FILE *xfopen (const char *path, const char *mode); +void xfclose (FILE *); + +__END_DECLS + +#endif /* SUPPORT_XSTDIO_H */ diff --git a/support/xstrdup.c b/support/xstrdup.c new file mode 100644 index 0000000..62650fa --- /dev/null +++ b/support/xstrdup.c @@ -0,0 +1,30 @@ +/* strdup with error checking. + Copyright (C) 2016 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 + . */ + +#include + +#include + +char * +xstrdup (const char *s) +{ + char *p = strdup (s); + if (p == NULL) + oom_error ("strdup", strlen (s)); + return p; +} diff --git a/support/xthread.h b/support/xthread.h index f0dc0fa..375d882 100644 --- a/support/xthread.h +++ b/support/xthread.h @@ -51,6 +51,7 @@ pthread_t xpthread_create (pthread_attr_t *attr, void xpthread_detach (pthread_t thr); void xpthread_cancel (pthread_t thr); void *xpthread_join (pthread_t thr); +void xpthread_once (pthread_once_t *guard, void (*func) (void)); /* This function returns non-zero if pthread_barrier_wait returned PTHREAD_BARRIER_SERIAL_THREAD. */ diff --git a/support/xunistd.h b/support/xunistd.h index f0c7419..0fe532a8 100644 --- a/support/xunistd.h +++ b/support/xunistd.h @@ -30,6 +30,9 @@ __BEGIN_DECLS pid_t xfork (void); pid_t xwaitpid (pid_t, int *status, int flags); +/* Write the buffer. Retry on short writes. */ +void xwrite (int, const void *, size_t); + __END_DECLS #endif /* SUPPORT_XUNISTD_H */ diff --git a/support/xwrite.c b/support/xwrite.c new file mode 100644 index 0000000..452359e --- /dev/null +++ b/support/xwrite.c @@ -0,0 +1,39 @@ +/* write with error checking and retries. + Copyright (C) 2016 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 + . */ + +#include + +#include + +void +xwrite (int fd, const void *buffer, size_t length) +{ + const char *p = buffer; + const char *end = p + length; + while (p < end) + { + ssize_t ret = write (fd, p, end - p); + if (ret < 0) + FAIL_EXIT1 ("write of %zu bytes failed after %zd: %m", + length, p - (const char *) buffer); + if (ret == 0) + FAIL_EXIT1 ("write return 0 after writing %zd bytes of %zu", + p - (const char *) buffer, length); + p += ret; + } +}