2
0
mirror of https://gitlab.isc.org/isc-projects/bind9 synced 2025-08-22 10:10:06 +00:00
bind/lib/dns/tests/dispatch_test.c
Ondřej Surý b43812692d Move netmgr/uv-compat.h to <isc/uv.h>
As we are going to use libuv outside of the netmgr, we need the shims to
be readily available for the rest of the codebase.

Move the "netmgr/uv-compat.h" to <isc/uv.h> and netmgr/uv-compat.c to
uv.c, and as a rule of thumb, the users of libuv should include
<isc/uv.h> instead of <uv.h> directly.

Additionally, merge netmgr/uverr2result.c into uv.c and rename the
single function from isc__nm_uverr2result() to isc_uverr2result().
2022-05-03 10:02:19 +02:00

754 lines
17 KiB
C

/*
* Copyright (C) Internet Systems Consortium, Inc. ("ISC")
*
* SPDX-License-Identifier: MPL-2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
*
* See the COPYRIGHT file distributed with this work for additional
* information regarding copyright ownership.
*/
#if HAVE_CMOCKA
#include <inttypes.h>
#include <sched.h> /* IWYU pragma: keep */
#include <setjmp.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define UNIT_TESTING
#include <cmocka.h>
#include <isc/buffer.h>
#include <isc/managers.h>
#include <isc/refcount.h>
#include <isc/task.h>
#include <isc/util.h>
#include <isc/uv.h>
#include <dns/dispatch.h>
#include <dns/name.h>
#include <dns/view.h>
#include "dnstest.h"
uv_sem_t sem;
/* Timeouts in miliseconds */
#define T_SERVER_INIT 5000
#define T_SERVER_IDLE 5000
#define T_SERVER_KEEPALIVE 5000
#define T_SERVER_ADVERTISED 5000
#define T_CLIENT_INIT 2000
#define T_CLIENT_IDLE 2000
#define T_CLIENT_KEEPALIVE 2000
#define T_CLIENT_ADVERTISED 2000
#define T_CLIENT_CONNECT 1000
dns_dispatchmgr_t *dispatchmgr = NULL;
dns_dispatchset_t *dset = NULL;
isc_nm_t *connect_nm = NULL;
static isc_sockaddr_t udp_server_addr;
static isc_sockaddr_t udp_connect_addr;
static isc_sockaddr_t tcp_server_addr;
static isc_sockaddr_t tcp_connect_addr;
const struct in6_addr in6addr_blackhole = { { { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 1 } } };
static int
setup_ephemeral_port(isc_sockaddr_t *addr, sa_family_t family) {
socklen_t addrlen = sizeof(*addr);
uv_os_sock_t fd = -1;
int r;
isc_sockaddr_fromin6(addr, &in6addr_loopback, 0);
fd = socket(AF_INET6, family, 0);
if (fd < 0) {
perror("setup_ephemeral_port: socket()");
return (-1);
}
r = bind(fd, (const struct sockaddr *)&addr->type.sa,
sizeof(addr->type.sin6));
if (r != 0) {
perror("setup_ephemeral_port: bind()");
close(fd);
return (r);
}
r = getsockname(fd, (struct sockaddr *)&addr->type.sa, &addrlen);
if (r != 0) {
perror("setup_ephemeral_port: getsockname()");
close(fd);
return (r);
}
#if IPV6_RECVERR
#define setsockopt_on(socket, level, name) \
setsockopt(socket, level, name, &(int){ 1 }, sizeof(int))
r = setsockopt_on(fd, IPPROTO_IPV6, IPV6_RECVERR);
if (r != 0) {
perror("setup_ephemeral_port");
close(fd);
return (r);
}
#endif
return (fd);
}
static void
reset_testdata(void);
static int
_setup(void **state) {
isc_result_t result;
uv_os_sock_t sock = -1;
int r;
UNUSED(state);
result = dns_test_begin(NULL, true);
assert_int_equal(result, ISC_R_SUCCESS);
udp_connect_addr = (isc_sockaddr_t){ .length = 0 };
isc_sockaddr_fromin6(&udp_connect_addr, &in6addr_loopback, 0);
tcp_connect_addr = (isc_sockaddr_t){ .length = 0 };
isc_sockaddr_fromin6(&tcp_connect_addr, &in6addr_loopback, 0);
udp_server_addr = (isc_sockaddr_t){ .length = 0 };
sock = setup_ephemeral_port(&udp_server_addr, SOCK_DGRAM);
if (sock < 0) {
return (-1);
}
close(sock);
tcp_server_addr = (isc_sockaddr_t){ .length = 0 };
sock = setup_ephemeral_port(&tcp_server_addr, SOCK_STREAM);
if (sock < 0) {
return (-1);
}
close(sock);
/* Create a secondary network manager */
isc_managers_create(dt_mctx, ncpus, 0, &connect_nm, NULL, NULL);
isc_nm_settimeouts(netmgr, T_SERVER_INIT, T_SERVER_IDLE,
T_SERVER_KEEPALIVE, T_SERVER_ADVERTISED);
/*
* Use shorter client-side timeouts, to ensure that clients
* time out before the server.
*/
isc_nm_settimeouts(connect_nm, T_CLIENT_INIT, T_CLIENT_IDLE,
T_CLIENT_KEEPALIVE, T_CLIENT_ADVERTISED);
r = uv_sem_init(&sem, 0);
assert_int_equal(r, 0);
reset_testdata();
return (0);
}
static int
_teardown(void **state) {
UNUSED(state);
uv_sem_destroy(&sem);
isc_managers_destroy(&connect_nm, NULL, NULL);
assert_null(connect_nm);
dns_test_end();
return (0);
}
static isc_result_t
make_dispatchset(unsigned int ndisps) {
isc_result_t result;
isc_sockaddr_t any;
dns_dispatch_t *disp = NULL;
result = dns_dispatchmgr_create(dt_mctx, netmgr, &dispatchmgr);
if (result != ISC_R_SUCCESS) {
return (result);
}
isc_sockaddr_any(&any);
result = dns_dispatch_createudp(dispatchmgr, &any, &disp);
if (result != ISC_R_SUCCESS) {
return (result);
}
result = dns_dispatchset_create(dt_mctx, disp, &dset, ndisps);
dns_dispatch_detach(&disp);
return (result);
}
static void
reset(void) {
if (dset != NULL) {
dns_dispatchset_destroy(&dset);
}
if (dispatchmgr != NULL) {
dns_dispatchmgr_detach(&dispatchmgr);
}
}
/* create dispatch set */
static void
dispatchset_create(void **state) {
isc_result_t result;
UNUSED(state);
result = make_dispatchset(1);
assert_int_equal(result, ISC_R_SUCCESS);
reset();
result = make_dispatchset(10);
assert_int_equal(result, ISC_R_SUCCESS);
reset();
}
/* test dispatch set round-robin */
static void
dispatchset_get(void **state) {
isc_result_t result;
dns_dispatch_t *d1, *d2, *d3, *d4, *d5;
UNUSED(state);
result = make_dispatchset(1);
assert_int_equal(result, ISC_R_SUCCESS);
d1 = dns_dispatchset_get(dset);
d2 = dns_dispatchset_get(dset);
d3 = dns_dispatchset_get(dset);
d4 = dns_dispatchset_get(dset);
d5 = dns_dispatchset_get(dset);
assert_ptr_equal(d1, d2);
assert_ptr_equal(d2, d3);
assert_ptr_equal(d3, d4);
assert_ptr_equal(d4, d5);
reset();
result = make_dispatchset(4);
assert_int_equal(result, ISC_R_SUCCESS);
d1 = dns_dispatchset_get(dset);
d2 = dns_dispatchset_get(dset);
d3 = dns_dispatchset_get(dset);
d4 = dns_dispatchset_get(dset);
d5 = dns_dispatchset_get(dset);
assert_ptr_equal(d1, d5);
assert_ptr_not_equal(d1, d2);
assert_ptr_not_equal(d2, d3);
assert_ptr_not_equal(d3, d4);
assert_ptr_not_equal(d4, d5);
reset();
}
struct {
atomic_uint_fast32_t responses;
atomic_uint_fast32_t result;
} testdata;
static dns_dispatch_t *dispatch = NULL;
static dns_dispentry_t *dispentry = NULL;
static atomic_bool first = true;
static void
reset_testdata(void) {
atomic_init(&testdata.responses, 0);
atomic_init(&testdata.result, ISC_R_UNSET);
}
static void
server_senddone(isc_nmhandle_t *handle, isc_result_t eresult, void *cbarg) {
UNUSED(handle);
UNUSED(eresult);
UNUSED(cbarg);
fprintf(stderr, "%s(..., %s, ...)\n", __func__,
isc_result_totext(eresult));
return;
}
static void
nameserver(isc_nmhandle_t *handle, isc_result_t eresult, isc_region_t *region,
void *cbarg) {
isc_region_t response;
static unsigned char buf1[16];
static unsigned char buf2[16];
UNUSED(eresult);
UNUSED(cbarg);
memmove(buf1, region->base, 12);
memset(buf1 + 12, 0, 4);
buf1[2] |= 0x80; /* qr=1 */
memmove(buf2, region->base, 12);
memset(buf2 + 12, 1, 4);
buf2[2] |= 0x80; /* qr=1 */
/*
* send message to be discarded.
*/
response.base = buf1;
response.length = sizeof(buf1);
isc_nm_send(handle, &response, server_senddone, NULL);
/*
* send nextitem message.
*/
response.base = buf2;
response.length = sizeof(buf2);
isc_nm_send(handle, &response, server_senddone, NULL);
}
static isc_result_t
accept_cb(isc_nmhandle_t *handle, isc_result_t eresult, void *cbarg) {
UNUSED(handle);
UNUSED(cbarg);
return (eresult);
}
static void
noop_nameserver(isc_nmhandle_t *handle, isc_result_t eresult,
isc_region_t *region, void *cbarg) {
UNUSED(handle);
UNUSED(eresult);
UNUSED(region);
UNUSED(cbarg);
}
static void
response_getnext(isc_result_t result, isc_region_t *region, void *arg) {
UNUSED(region);
UNUSED(arg);
atomic_fetch_add_relaxed(&testdata.responses, 1);
if (atomic_compare_exchange_strong(&first, &(bool){ true }, false)) {
result = dns_dispatch_getnext(dispentry);
assert_int_equal(result, ISC_R_SUCCESS);
} else {
uv_sem_post(&sem);
}
}
static void
response(isc_result_t eresult, isc_region_t *region, void *arg) {
UNUSED(region);
UNUSED(arg);
fprintf(stderr, "%s(..., %s, ...)\n", __func__,
isc_result_totext(eresult));
switch (eresult) {
case ISC_R_EOF:
case ISC_R_CANCELED:
case ISC_R_SHUTTINGDOWN:
break;
default:
atomic_fetch_add_relaxed(&testdata.responses, 1);
atomic_store_relaxed(&testdata.result, eresult);
}
uv_sem_post(&sem);
}
static void
response_timeout(isc_result_t eresult, isc_region_t *region, void *arg) {
UNUSED(region);
UNUSED(arg);
fprintf(stderr, "%s(..., %s, ...)\n", __func__,
isc_result_totext(eresult));
atomic_store_relaxed(&testdata.result, eresult);
uv_sem_post(&sem);
}
static void
connected(isc_result_t eresult, isc_region_t *region, void *cbarg) {
isc_region_t *r = (isc_region_t *)cbarg;
UNUSED(eresult);
UNUSED(region);
fprintf(stderr, "%s(..., %s, ...)\n", __func__,
isc_result_totext(eresult));
dns_dispatch_send(dispentry, r, -1);
}
static void
client_senddone(isc_result_t eresult, isc_region_t *region, void *cbarg) {
UNUSED(eresult);
UNUSED(region);
UNUSED(cbarg);
fprintf(stderr, "%s(..., %s, ...)\n", __func__,
isc_result_totext(eresult));
return;
}
static void
timeout_connected(isc_result_t eresult, isc_region_t *region, void *cbarg) {
UNUSED(region);
UNUSED(cbarg);
fprintf(stderr, "%s(..., %s, ...)\n", __func__,
isc_result_totext(eresult));
atomic_store_relaxed(&testdata.result, eresult);
uv_sem_post(&sem);
}
static void
dispatch_timeout_tcp_connect(void **state) {
isc_result_t result;
isc_region_t region;
unsigned char rbuf[12] = { 0 };
unsigned char message[12] = { 0 };
uint16_t id;
UNUSED(state);
tcp_connect_addr = (isc_sockaddr_t){ .length = 0 };
isc_sockaddr_fromin6(&tcp_connect_addr, &in6addr_blackhole, 0);
result = dns_dispatchmgr_create(dt_mctx, connect_nm, &dispatchmgr);
assert_int_equal(result, ISC_R_SUCCESS);
result = dns_dispatch_createtcp(dispatchmgr, &tcp_connect_addr,
&tcp_server_addr, -1, &dispatch);
assert_int_equal(result, ISC_R_SUCCESS);
region.base = rbuf;
region.length = sizeof(rbuf);
result = dns_dispatch_add(dispatch, 0, T_CLIENT_CONNECT,
&tcp_server_addr, timeout_connected,
client_senddone, response, &region, &id,
&dispentry);
assert_int_equal(result, ISC_R_SUCCESS);
memset(message, 0, sizeof(message));
message[0] = (id >> 8) & 0xff;
message[1] = id & 0xff;
region.base = message;
region.length = sizeof(message);
dns_dispatch_connect(dispentry);
uv_sem_wait(&sem);
dns_dispatch_done(&dispentry);
dns_dispatch_detach(&dispatch);
dns_dispatchmgr_detach(&dispatchmgr);
/* Skip if the IPv6 is not available or not blackholed */
result = atomic_load_acquire(&testdata.result);
if (result == ISC_R_ADDRNOTAVAIL || result == ISC_R_CONNREFUSED) {
skip();
return;
}
assert_int_equal(result, ISC_R_TIMEDOUT);
}
static void
dispatch_timeout_tcp_response(void **state __attribute__((unused))) {
isc_result_t result;
isc_region_t region;
unsigned char rbuf[12] = { 0 };
unsigned char message[12] = { 0 };
uint16_t id;
isc_nmsocket_t *sock = NULL;
UNUSED(state);
tcp_connect_addr = (isc_sockaddr_t){ .length = 0 };
isc_sockaddr_fromin6(&tcp_connect_addr, &in6addr_loopback, 0);
result = dns_dispatchmgr_create(dt_mctx, connect_nm, &dispatchmgr);
assert_int_equal(result, ISC_R_SUCCESS);
result = dns_dispatch_createtcp(dispatchmgr, &tcp_connect_addr,
&tcp_server_addr, -1, &dispatch);
assert_int_equal(result, ISC_R_SUCCESS);
result = isc_nm_listentcpdns(netmgr, ISC_NM_LISTEN_ONE,
&tcp_server_addr, noop_nameserver, NULL,
accept_cb, NULL, 0, NULL, &sock);
assert_int_equal(result, ISC_R_SUCCESS);
region.base = rbuf;
region.length = sizeof(rbuf);
result = dns_dispatch_add(dispatch, 0, T_CLIENT_CONNECT,
&tcp_server_addr, connected, client_senddone,
response_timeout, &region, &id, &dispentry);
assert_int_equal(result, ISC_R_SUCCESS);
memset(message, 0, sizeof(message));
message[0] = (id >> 8) & 0xff;
message[1] = id & 0xff;
region.base = message;
region.length = sizeof(message);
dns_dispatch_connect(dispentry);
uv_sem_wait(&sem);
assert_int_equal(atomic_load_acquire(&testdata.result), ISC_R_TIMEDOUT);
isc_nm_stoplistening(sock);
isc_nmsocket_close(&sock);
assert_null(sock);
dns_dispatch_done(&dispentry);
dns_dispatch_detach(&dispatch);
dns_dispatchmgr_detach(&dispatchmgr);
}
static void
dispatch_tcp_response(void **state __attribute__((unused))) {
isc_result_t result;
isc_region_t region;
unsigned char rbuf[12] = { 0 };
unsigned char message[12] = { 0 };
uint16_t id;
isc_nmsocket_t *sock = NULL;
UNUSED(state);
tcp_connect_addr = (isc_sockaddr_t){ .length = 0 };
isc_sockaddr_fromin6(&tcp_connect_addr, &in6addr_loopback, 0);
result = dns_dispatchmgr_create(dt_mctx, connect_nm, &dispatchmgr);
assert_int_equal(result, ISC_R_SUCCESS);
result = dns_dispatch_createtcp(dispatchmgr, &tcp_connect_addr,
&tcp_server_addr, -1, &dispatch);
assert_int_equal(result, ISC_R_SUCCESS);
result = isc_nm_listentcpdns(netmgr, ISC_NM_LISTEN_ONE,
&tcp_server_addr, nameserver, NULL,
accept_cb, NULL, 0, NULL, &sock);
assert_int_equal(result, ISC_R_SUCCESS);
region.base = rbuf;
region.length = sizeof(rbuf);
result = dns_dispatch_add(dispatch, 0, T_CLIENT_CONNECT,
&tcp_server_addr, connected, client_senddone,
response, &region, &id, &dispentry);
assert_int_equal(result, ISC_R_SUCCESS);
memset(message, 0, sizeof(message));
message[0] = (id >> 8) & 0xff;
message[1] = id & 0xff;
region.base = message;
region.length = sizeof(message);
dns_dispatch_connect(dispentry);
uv_sem_wait(&sem);
assert_in_range(atomic_load_acquire(&testdata.responses), 1, 2);
assert_int_equal(atomic_load_acquire(&testdata.result), ISC_R_SUCCESS);
/* Cleanup */
isc_nm_stoplistening(sock);
isc_nmsocket_close(&sock);
assert_null(sock);
dns_dispatch_done(&dispentry);
dns_dispatch_detach(&dispatch);
dns_dispatchmgr_detach(&dispatchmgr);
}
static void
dispatch_timeout_udp_response(void **state __attribute__((unused))) {
isc_result_t result;
isc_region_t region;
unsigned char rbuf[12] = { 0 };
unsigned char message[12] = { 0 };
uint16_t id;
isc_nmsocket_t *sock = NULL;
UNUSED(state);
udp_connect_addr = (isc_sockaddr_t){ .length = 0 };
isc_sockaddr_fromin6(&udp_connect_addr, &in6addr_loopback, 0);
result = dns_dispatchmgr_create(dt_mctx, connect_nm, &dispatchmgr);
assert_int_equal(result, ISC_R_SUCCESS);
result = dns_dispatch_createudp(dispatchmgr, &tcp_connect_addr,
&dispatch);
assert_int_equal(result, ISC_R_SUCCESS);
result = isc_nm_listenudp(netmgr, ISC_NM_LISTEN_ONE, &udp_server_addr,
noop_nameserver, NULL, &sock);
assert_int_equal(result, ISC_R_SUCCESS);
region.base = rbuf;
region.length = sizeof(rbuf);
result = dns_dispatch_add(dispatch, 0, T_CLIENT_CONNECT,
&udp_server_addr, connected, client_senddone,
response_timeout, &region, &id, &dispentry);
assert_int_equal(result, ISC_R_SUCCESS);
memset(message, 0, sizeof(message));
message[0] = (id >> 8) & 0xff;
message[1] = id & 0xff;
region.base = message;
region.length = sizeof(message);
dns_dispatch_connect(dispentry);
uv_sem_wait(&sem);
assert_int_equal(atomic_load_acquire(&testdata.result), ISC_R_TIMEDOUT);
isc_nm_stoplistening(sock);
isc_nmsocket_close(&sock);
assert_null(sock);
dns_dispatch_done(&dispentry);
dns_dispatch_detach(&dispatch);
dns_dispatchmgr_detach(&dispatchmgr);
}
/* test dispatch getnext */
static void
dispatch_getnext(void **state) {
isc_result_t result;
isc_region_t region;
isc_nmsocket_t *sock = NULL;
unsigned char message[12] = { 0 };
unsigned char rbuf[12] = { 0 };
uint16_t id;
UNUSED(state);
result = dns_dispatchmgr_create(dt_mctx, connect_nm, &dispatchmgr);
assert_int_equal(result, ISC_R_SUCCESS);
result = dns_dispatch_createudp(dispatchmgr, &udp_connect_addr,
&dispatch);
assert_int_equal(result, ISC_R_SUCCESS);
/*
* Create a local udp nameserver on the loopback.
*/
result = isc_nm_listenudp(netmgr, ISC_NM_LISTEN_ONE, &udp_server_addr,
nameserver, NULL, &sock);
assert_int_equal(result, ISC_R_SUCCESS);
region.base = rbuf;
region.length = sizeof(rbuf);
result = dns_dispatch_add(dispatch, 0, T_CLIENT_CONNECT,
&udp_server_addr, connected, client_senddone,
response_getnext, &region, &id, &dispentry);
assert_int_equal(result, ISC_R_SUCCESS);
memset(message, 0, sizeof(message));
message[0] = (id >> 8) & 0xff;
message[1] = id & 0xff;
region.base = message;
region.length = sizeof(message);
dns_dispatch_connect(dispentry);
uv_sem_wait(&sem);
assert_int_equal(atomic_load_acquire(&testdata.responses), 2);
/* Cleanup */
isc_nm_stoplistening(sock);
isc_nmsocket_close(&sock);
assert_null(sock);
dns_dispatch_done(&dispentry);
dns_dispatch_detach(&dispatch);
dns_dispatchmgr_detach(&dispatchmgr);
}
int
main(void) {
const struct CMUnitTest tests[] = {
cmocka_unit_test_setup_teardown(dispatch_timeout_tcp_connect,
_setup, _teardown),
cmocka_unit_test_setup_teardown(dispatch_timeout_tcp_response,
_setup, _teardown),
cmocka_unit_test_setup_teardown(dispatch_tcp_response, _setup,
_teardown),
cmocka_unit_test_setup_teardown(dispatch_timeout_udp_response,
_setup, _teardown),
cmocka_unit_test_setup_teardown(dispatchset_create, _setup,
_teardown),
cmocka_unit_test_setup_teardown(dispatchset_get, _setup,
_teardown),
cmocka_unit_test_setup_teardown(dispatch_getnext, _setup,
_teardown),
};
return (cmocka_run_group_tests(tests, NULL, NULL));
}
#else /* HAVE_CMOCKA */
#include <stdio.h>
int
main(void) {
printf("1..0 # Skipped: cmocka not available\n");
return (SKIPPED_TEST_EXIT_CODE);
}
#endif /* if HAVE_CMOCKA */